寫好C#代碼的技巧
寫好C#代碼的技巧
編者導(dǎo)語
本文來自https://www.pluralsight.com,作者Afzaal Ahmad Zeeshan。
原文包含以下三篇文章:
《編寫更好的C#代碼簡介》https://www.pluralsight.com/guides/introduction-to-writing-better-csharp-code
《編寫更好的C#代碼的技巧》https://www.pluralsight.com/guides/tips-for-writing-better-c-code
《有關(guān)編寫更好的C#代碼的更多技巧》https://www.pluralsight.com/guides/more-tips-for-writing-better-csharp-code
雖然本文僅介紹了C#6.0語言特性,而現(xiàn)在最新的C#已經(jīng)到了9.0,但這些內(nèi)容已經(jīng)仍然常讀常新。
一、簡介
C#已從C#5更改為C#6,為使項(xiàng)目更具可讀性,基于最佳標(biāo)準(zhǔn)的實(shí)踐也得到了發(fā)展。
本指南系列的目的是幫助您為在團(tuán)隊(duì)環(huán)境中運(yùn)行的C#項(xiàng)目和.NET Framework應(yīng)用程序編寫更簡潔的代碼。在團(tuán)隊(duì)環(huán)境下,編寫好的代碼對開發(fā)人員可能更容易,因?yàn)榫帉懙拇a將由團(tuán)隊(duì)中其他開發(fā)人員使用,管理和更新,而代碼質(zhì)量往往取決于您個(gè)人團(tuán)隊(duì)的“哲學(xué)”和開發(fā)人員的編碼實(shí)踐。
在這種情況下,最好的方法是遵循編碼團(tuán)隊(duì)的準(zhǔn)則,并為應(yīng)用程序項(xiàng)目中的C#程序添加設(shè)計(jì)和風(fēng)格,以使它們對讀者更好。請注意,C#編譯器并不關(guān)心您放入代碼中的風(fēng)格。但我們以一種使C#應(yīng)用程序?qū)ψx者來說看起來更簡單,更清潔,將更容易的方式更深入地進(jìn)行編程,同時(shí)保持代碼開發(fā)的性能和效率。
在閱讀本指南之前,您應(yīng)該了解以下幾點(diǎn):
1.第6版對C#的改進(jìn)2..NET框架中的LINQ3.TaskC#中的異步編程和對象4.使用C#進(jìn)行的不安全編程,使您無法正常的使用內(nèi)存管理
不專注于性能
應(yīng)該注意的是,我不會(huì)談?wù)摳淖兂绦蛐阅埽岣咝驶驕p少程序運(yùn)行所花費(fèi)的時(shí)間。通過編寫簡潔的C#代碼,您可以在幾秒鐘內(nèi)提高程序性能,但是以下技巧并不能保證您的代碼性能更好。
為什么要編寫整潔的代碼?
您編寫代碼,編譯器編譯時(shí)沒有警告也沒有錯(cuò)誤,代碼很好。但是,如果其他人想讀出該代碼怎么辦?如果有人后來需要為您或您所在的公司升級代碼,該怎么辦?看下面的代碼:
public static void Main(string[] args) {int x = 0;x = Console.Read();Console.WriteLine(x * 1.5);}
該程序運(yùn)行良好,系統(tǒng)中沒有錯(cuò)誤,應(yīng)用程序也可以正常工作。但是您能告訴我該程序在現(xiàn)實(shí)生活中做什么嗎?以下是可以做出的一些假設(shè):
1.它只是乘以價(jià)值2.就像獎(jiǎng)金一樣,它正在增加價(jià)值3.是個(gè)人銀行存款總額的利率4.等等。
哪一個(gè)是真實(shí)的?沒有人會(huì)知道。在這種情況下,最好編寫出良好的代碼,并記住遵循編程的基礎(chǔ)。看下面的代碼:
public static void Main(string[] args) {int salary = 0;salary = Console.Read();Console.WriteLine(salary * 1.5);}
這比以前的代碼有意義嗎?我們可以很容易地說這個(gè)代碼將增加薪水的價(jià)值。請注意,僅通過改進(jìn)代碼,我們就能確保其他人可以比以前更快地理解它。
在本指南中,我不會(huì)向您展示如何遵循最佳原則。相反,我將以您已有的知識為基礎(chǔ),并教您如何充分利用C#程序。我將重點(diǎn)介紹如何在應(yīng)用程序中編寫良好的C#邏輯,因此您將看到通過以這種方式和結(jié)構(gòu)編寫程序,可以從應(yīng)用程序中獲得很多好處。
因此,讓我們開始吧。
對象初始化
C#是一種面向?qū)ο蟮木幊陶Z言。如果對象本身沒有分塊,那么寫一組提示有什么好處?本節(jié)將重點(diǎn)介紹在前進(jìn)并new Object()在應(yīng)用程序中編寫代碼之前應(yīng)考慮的事項(xiàng)。您必須了解如何創(chuàng)建C#類以及事物如何協(xié)作以在系統(tǒng)中啟動(dòng)一個(gè)小程序。
例如,看下面的代碼:
class Person {public int ID { get; set; }public string Name { get; set; }public DateTime DateOfBirth { get; set; }public bool Gender { get; set; }}
您可能想要?jiǎng)?chuàng)建默認(rèn)情況下設(shè)置值的程序,或者讓它們來自模型或諸如此源代碼的任何其他面向數(shù)據(jù)庫的數(shù)據(jù)源,這些程序簡化了在對象時(shí)輸入默認(rèn)值的方式正在創(chuàng)建。
var person = new Person { ID = 1, Name = "Afzaal Ahmad Zeeshan", DateOfBirth = new DateTime(1995, 08, 29), Gender = true };
相反,請嘗試通過以下方式編寫相同的代碼:
var person = new Person();person.ID = 1;person.Name = "Afzaal Ahmad Zeeshan";// So on.
這里的代碼沒有明顯的性能改進(jìn),但是可以真正提高代碼的可讀性。如果您喜歡縮進(jìn),請?jiān)谶@里查看:
var person = new Person{ID = 1,Name = "Afzaal Ahmad Zeeshan",DateOfBirth = new DateTime(1995, 08, 29),Gender = true};
這也有縮進(jìn),但是它為您的C#代碼的可讀性添加了更多的說明。盡管前面的代碼可以實(shí)現(xiàn)相同的功能,但是建議的代碼可以使代碼更易讀和簡潔。
二、技巧
空檢查
NullReferenceException當(dāng)缺少初始化的對象再次拋出異常時(shí),您是否曾經(jīng)對感到惱火?在程序中進(jìn)行空檢查有很多好處,不僅可以提高可讀性,而且可以確保程序不會(huì)由于內(nèi)存問題而終止(例如,內(nèi)存中不存在變量時(shí))。這些可能與程序的安全性以及團(tuán)隊(duì)具有的良好UI和UX準(zhǔn)則相抵觸。大多數(shù)情況下,由于以下原因會(huì)引發(fā)空異常:
string name = null;Console.WriteLine(name);
在大多數(shù)情況下,除非您解決此問題,否則編譯器本身不會(huì)繼續(xù)運(yùn)行,但是如果您設(shè)法以某種方式誘使編譯器認(rèn)為變量具有值,但在運(yùn)行時(shí)沒有變量,則會(huì)出現(xiàn)空引用異常。為了克服這個(gè)問題,您可以執(zhí)行以下操作:
string name = null;// Try to enter the value, from somewhereif(name != null) {Console.WriteLine(name);}
此安全檢查將確保在調(diào)用此變量時(shí)該值可用。否則,它將影響您代碼的路徑。但是,在C#6中,還有另一種方法可以克服此錯(cuò)誤??紤]以下情形:建立數(shù)據(jù)庫,建立數(shù)據(jù)表,找到您的人員但找不到他們的就業(yè)詳細(xì)信息。你能找到他們工作的公司嗎?
var company = DbHelper.PeopleTable.Find(x => x.id == id).FirstOrDefault().EmploymentHistory.CompanyName; // Error
如果您這樣做,將會(huì)出現(xiàn)錯(cuò)誤,因?yàn)槲覀冎荒茉谶@些值的列表中進(jìn)行簡單幾步的對象篩選。然后我們將碰到一個(gè)空值,一切都丟失了。C#6提出了一種克服這些情況的新方法,方法是在值和字段可以為null的后面使用安全的導(dǎo)航運(yùn)算符。?.。像這樣:
var company = DbHelper?.PeopleTable?.Find(x => x.id == id)?.FirstOrDefault()?.EmploymentHistory?.CompanyName; // Works
如果前一個(gè)不為null,則此代碼僅檢查下一個(gè)值。如果先前的值為null,它將返回null并將null保存為的值company,而不是引發(fā)錯(cuò)誤。將檢查留給框架本身可以很方便,但是,盡管如此,您仍然必須在最后檢查其余值是否為null。
var company = DbHelper.PeopleTable?.Find(x => x.id == id)?.FirstOrDefault()?.EmploymentHistory?.CompanyName;if(company != null) {// Final process}
但是您明白了這一點(diǎn),而不是編寫代碼并檢查所有內(nèi)容是否為空,而是可以執(zhí)行簡單的檢查并執(zhí)行程序中想要的操作和邏輯。否則,將需要try...catch包裝器或多個(gè)if...else塊來控制程序在系統(tǒng)中的導(dǎo)航方式。
異步編程模式
如果您正在使用C#5進(jìn)行編程,那么您已經(jīng)在使用async / await關(guān)鍵字為您的應(yīng)用程序帶來改進(jìn)。如果不是這種情況,那么我建議您在應(yīng)用程序的源代碼中使用異步編程模式。這不僅可以提高對程序的響應(yīng)速度,還可以提高應(yīng)用程序的可讀性。在源代碼中具有異步模式的一些好處是:
1.代碼路徑開始變得更加有意義。如果有一個(gè)進(jìn)程在后臺開始運(yùn)行,那么程序員可以了解程序應(yīng)該在哪里。2.應(yīng)用程序掛起問題將消失。大多數(shù)與應(yīng)用程序阻塞相關(guān)的問題直接來自代碼。當(dāng)UI線程無法更新UI時(shí),用戶會(huì)認(rèn)為該應(yīng)用程序正在掛起并且沒有響應(yīng),而事實(shí)并非如此。異步方法確實(shí)可以幫上大忙。3.基于Windows運(yùn)行時(shí)的應(yīng)用程序完全基于此方法。您將(并且必須是!)在您的Windows Runtime應(yīng)用程序中使用這種方法來解決諸如掛起應(yīng)用程序或不良的編程習(xí)慣之類的問題。
自從線程化以來,代碼執(zhí)行的并行化就已經(jīng)存在。異步已經(jīng)成為程序和應(yīng)用程序的重要組成部分,因此您更應(yīng)該考慮使用它。
C#字符串構(gòu)建
字符串是當(dāng)今應(yīng)用程序的重要組成部分,構(gòu)建字符串可能會(huì)花費(fèi)很多時(shí)間,并且還會(huì)導(dǎo)致應(yīng)用程序性能下降。您可以通過多種方式在C#程序中構(gòu)建字符串。以下是其中幾種方式:
string str = ""; // Setting it to null would cause additional problems.// Way 1str = "Name: " + name + ", Age: " + age;// Way 2str = string.Format("Name: {0}, Age: {1}", name, age);// Way 3var builder = new StringBuilder();builder.Append("Name: ");builder.Append(name);builder.Append(", Age: ");builder.Append(age);str = builder.ToString();
請注意,C#中的字符串是不可變的。這意味著,如果您嘗試更新它們的值,則會(huì)重新創(chuàng)建它們,并從內(nèi)存中刪除以前的句柄。這就是為什么方式1看起來是最好的方式,但經(jīng)過進(jìn)一步思考,事實(shí)并非如此。最好的方法是方法3,它使您可以構(gòu)建字符串而不必在內(nèi)存中重新創(chuàng)建對象。同時(shí),C#6引入了一種全新的方式在C#中構(gòu)建字符串,該方式比您以前想象的要好得多。新的
字符串插值 運(yùn)算符$為您提供了以最佳方式執(zhí)行字符串構(gòu)建的功能。字符串插值如下所示:
static void Main(string[] args){// Just arbitrary variablesstring name = "";int age = 0;// Our intereststring str = $"Name: {name}, Age: {age}";}
只需一行代碼,編譯器就會(huì)自動(dòng)將其轉(zhuǎn)換為string.Format()版本。為了證明這一點(diǎn),將詳細(xì)說明此C#程序已生成的字節(jié)碼,并向您展示如何自動(dòng)更改語法以讀取字符串格式。
IL_0000: nopIL_0001: ldstr ""IL_0006: stloc.0 // nameIL_0007: ldc.i4.0IL_0008: stloc.1 // ageIL_0009: ldstr "Name: {0}, Age: {1}"IL_000E: ldloc.0 // nameIL_000F: ldloc.1 // ageIL_0010: box System.Int32IL_0015: call System.String.FormatIL_001A: stloc.2 // strIL_001B: ret
可以看出,這顯示了如何將語法更改回我們已經(jīng)看到的語法。有關(guān)IL_0009更多信息,請參見。當(dāng)其他人正在讀取程序時(shí),這可以使您的程序外觀更簡潔,并且如果要構(gòu)建的字符串較小,則可以提高性能。如果字符串較大,請使用StringBuilder。
三、更多技巧
遍歷數(shù)據(jù)
如果不對一組數(shù)據(jù)進(jìn)行循環(huán)和迭代,那么應(yīng)用程序有什么用?在這種情況下,有時(shí)您將不得不查找值,查找節(jié)點(diǎn),查找記錄或?qū)线M(jìn)行任何其他遍歷。在這種情況下,您確實(shí)需要確保編寫干凈的代碼,因?yàn)檫@是性能和可讀性都非常重要且相互關(guān)聯(lián)的領(lǐng)域。
有了一些經(jīng)驗(yàn),我就克服了編寫用于讀取和遍歷數(shù)據(jù)的錯(cuò)誤代碼的方式。這正是LINQ應(yīng)該加入的地方,LINQ允許您編寫使用最佳.NET框架為用戶和客戶提供最佳編碼體驗(yàn)和最佳體驗(yàn)的程序。
以前,您可能已經(jīng)做過以下一些事情:
6// A function to search for peoplePerson FindPerson(int id) {var people = DbContext.GetPeople(); // Returns Listforeach (var person in people) {if(person.ID == id) {return person;}}// No person found.return null;}// Then do thisvar person = FindPerson(123);
對于任何想接手您代碼的人來說,這都是一段易讀的代碼。但是,使用C#中的LINQ查詢可以使代碼更加簡單和整潔。您可以通過兩種方式執(zhí)行此操作。一個(gè)有點(diǎn)像SQL,另一個(gè)是通過Where在集合上使用該函數(shù)并傳遞我們的要求。
// A function to search for peoplePerson FindPerson(int id) {var people = DbContext.GetPeople(); // Returns Listreturn (from person in peoplewhere person.ID == idselect person).ToList().FirstOrDefault();}// Then do thisvar person = FindPerson(123);
該代碼看起來有點(diǎn)像SQL,可以增強(qiáng)代碼的可讀性和性能。該函數(shù)相似,但是,該Where函數(shù)的讀取效果更好,并使所有迭代都針對.NET框架本身,而.NET框架將為應(yīng)用程序提供最佳性能。
現(xiàn)在,讓我們看看用相同的C#代碼編寫此查詢的另一種方式:
// A function to search for peoplePerson FindPerson(int id) {var people = DbContext.GetPeople(); // Returns Listreturn people.FirstOrDefault(x => x.ID == id);}// Then do thisvar person = FindPerson(123);
請注意,null如果沒有找到匹配項(xiàng),則返回第一個(gè)代碼。這段代碼也做同樣的事情。唯一的第一個(gè)代碼更糟糕的是它必須對集合本身執(zhí)行迭代。
該本地變量return person;將允許程序返回控件,但是如果數(shù)據(jù)位于最后一個(gè)位置會(huì)發(fā)生什么呢?此數(shù)據(jù)搜索算法的復(fù)雜度仍為O(n)。
避免unsafe上下文
在您必須親自處理內(nèi)存時(shí),C#還支持手動(dòng)內(nèi)存管理。C#中的不安全上下文允許您操作內(nèi)存,執(zhí)行指針?biāo)阈g(shù),在可能無法訪問的內(nèi)存位置讀取和寫入數(shù)據(jù),等等。但是,.NET框架可以做很多事情來克服內(nèi)存問題,延遲和磁盤上其他問題。這也使.NET框架完全無需實(shí)際執(zhí)行任何內(nèi)存管理,.NET框架將為您做到這一點(diǎn)。
使用不安全的上下文有很多好處,例如,當(dāng)您要圍繞本機(jī)C ++庫編寫包裝器時(shí)。Emgu CV就是這樣一個(gè)示例,您將在其中編寫一些代碼來處理如何管理本機(jī)代碼,并以更簡單的方式來處理內(nèi)存中的錯(cuò)誤。在這種情況下,您可以:
1.使用指針管理和指針?biāo)阈g(shù)。您不能在此上下文之外的任何地址上執(zhí)行任何操作,這是.NET規(guī)則所處的位置。2.使用內(nèi)存管理來操作內(nèi)存中的對象。3.使用C ++風(fēng)格的編程,這正是C#設(shè)計(jì)的目的。
這幾乎沒有好處,如果您應(yīng)該在應(yīng)用程序中考慮這一點(diǎn),請明智地考慮。
關(guān)于Unsafe純屬個(gè)人觀點(diǎn)
我還想指出,關(guān)于“不安全”的利弊,我所說的一切都是我個(gè)人的看法。我不經(jīng)常在程序中使用unsafe上下文,因?yàn)闆]有理由考慮在應(yīng)用程序中使用上下文。但是,如果您的應(yīng)用程序需要本機(jī)內(nèi)存管理,則可以使用此上下文。
盡可能使用Lambda表達(dá)式
Lambda來自函數(shù)式編程領(lǐng)域,在C#中已廣泛使用,從內(nèi)聯(lián)函數(shù)一直到C#6中的getter only屬性。我將展示C#中的兩種用法,它們構(gòu)成的程序,不僅看起來更清爽,而且性能指標(biāo)也更高。
為此,我將向您顯示該C#代碼的IL。我個(gè)人喜歡在許多領(lǐng)域使用lambda,尤其是當(dāng)我不得不用C#編寫內(nèi)聯(lián)函數(shù)時(shí)。自從可以使用此概念編寫僅用于getter的屬性以來,我一直在使用它們,并且我個(gè)人認(rèn)為它比以前做同一件事的方法更好。
1.將Lambda用于內(nèi)聯(lián)函數(shù)
您應(yīng)該知道一些C#編程的示例,使用這種寫法的代碼很多。
例如在應(yīng)用程序中進(jìn)行事件處理的情況下,對于事件處理,您可以像下面這樣編寫當(dāng)前函數(shù):
// Without lamdbasmyBtn.Click += Btn_Click;public void Btn_Click (object sender, EventArgs e) {// Code to handle the event}// With the help of lambdasmyBtn.Click += (sender, e) =>{// Code to handle the event.}
請注意,編譯器將自動(dòng)將對象映射到其類型。這在許多方面都很方便,因?yàn)樗试S您用C#編寫僅與對象一起保留的內(nèi)聯(lián)函數(shù),除非您也想在其他任何地方使用它們。但是,這種處理事件的方法有一個(gè)缺點(diǎn):一旦附加了事件處理程序,便無法刪除它。在C#中可以,-+。
但是由于我們沒有刪除事件的參考,因此只能使用單獨(dú)的函數(shù)。但是,如果不必刪除處理程序,則應(yīng)始終考慮在程序中使用這種事件處理方式。
2.將Lambda用于僅Getter的屬性
在C#中,有一個(gè)使用屬性而不是字段的概念。您可以控制如何設(shè)置值以及如何從字段中捕獲值。將其視為Java編程語言的getter和setter方法的替代方法(或類似方法)。唯一的區(qū)別是您不必在某個(gè)地方分別編寫它們,它們直接寫在字段本身的前面。然后,C#程序編譯器將創(chuàng)建自己的后備字段,用于存儲值。
基本上,您必須編寫如下這樣的屬性:
public string Name { get; }
請注意,這些屬性是恒定的,設(shè)置后就無法更改。它們是在構(gòu)造函數(shù)中設(shè)置的,或者(從C#6開始)在它們的前面設(shè)置。像這樣:
public string Name { get; } = "Afzaal Ahmad Zeeshan";
但是,由于我們已經(jīng)知道這是一個(gè)常量字段,您不能修改它,那么為什么不創(chuàng)建一個(gè)簡單的常量屬性呢?事情變得有些棘手。甚至一個(gè)屬性也必須由字段來備份。在這種情況下,這將為我們解決問題:
public string Name => "Afzaal Ahmad Zeeshan";
這等效于編寫以下內(nèi)容:
public string Name { get { return "Afzaal Ahmad Zeeshan"; } }
但是由于編譯期將getter字段轉(zhuǎn)換為常量字段,并且在必須調(diào)用此屬性的時(shí)候才會(huì)在程序中使用該字段,因此性能要好得多。
最后的話
本指南系列的目的是使您了解一些使程序更易于閱讀和更好執(zhí)行的方法。C#編譯器本身會(huì)盡最大努力提高代碼的質(zhì)量和效率,而這程序員帶來便利,同時(shí)也將使程序更好地工作。
除了上面提到的方法,還有許多其他提高可讀性的方法,其中許多方法適合公司團(tuán)隊(duì)協(xié)作的形式編寫程序,因?yàn)榇蠖鄶?shù)團(tuán)隊(duì)往往都要求程序員遵循自己的編程方法和方式。
副業(yè)剛需,沒有人能拒絕這個(gè)網(wǎng)站!
微信錢包“免費(fèi)提現(xiàn)”的方法來了!
