您好,登錄后才能下訂單哦!
本文講述了C#開發(fā)人員應(yīng)該了解到的13件事情,希望對(duì)C#開發(fā)人員有所幫助。
開發(fā)過程是錯(cuò)誤和缺陷開始的地方。使用工具可以幫助你在發(fā)布之后,解決掉一些問題。
編碼標(biāo)準(zhǔn)
遵照編碼標(biāo)準(zhǔn)可以編寫出更多可維護(hù)的代碼,特別是在由多個(gè)開發(fā)人員或團(tuán)隊(duì)編寫和維護(hù)的代碼庫(kù)中。例如FxCop,StyleCop和ReSharper等,就是常用的實(shí)施編碼標(biāo)準(zhǔn)的工具。
開發(fā)人員:在壓縮代碼之前,請(qǐng)使用工具仔細(xì)檢查是否違反了標(biāo)準(zhǔn),并且對(duì)結(jié)果進(jìn)行分析。使用工具發(fā)現(xiàn)的代碼路徑問題,不比你預(yù)期的少。
代碼審查
代碼審查和結(jié)對(duì)編程是任務(wù)開發(fā)人員審查他人編寫的源代碼的常見做法。通過這些方式希望能夠檢查出作者的錯(cuò)誤,如編碼錯(cuò)誤或?qū)崿F(xiàn)錯(cuò)誤。
代碼審查是一個(gè)很有價(jià)值的做法,但是它依賴于人類,易犯錯(cuò)誤,所以很難擴(kuò)展。
靜態(tài)分析
靜態(tài)分析工具會(huì)在不運(yùn)行代碼的情況下分析代碼,在不需要編寫測(cè)試用例的情況下,查找違反編碼標(biāo)準(zhǔn)或存在缺陷的問題。它們能有效地找到問題,但你需要選擇出那些能夠定位出有價(jià)值問題的工具,找出有價(jià)值的問題。C#靜態(tài)分析工具包括Coverity,CAT.NET和Visual Studio代碼分析。
動(dòng)態(tài)分析
動(dòng)態(tài)分析工具在運(yùn)行時(shí)分析代碼,幫助你查找缺陷,如安全漏洞,性能和并發(fā)問題。它分析運(yùn)行時(shí)環(huán)境的上下文中的代碼,因此其有效性受測(cè)試工作負(fù)載的限制。Visual Studio提供了一些動(dòng)態(tài)分析工具,包括并發(fā)可視化器,IntelliTrace和分析工具。
管理人員/團(tuán)隊(duì)領(lǐng)導(dǎo):利用開發(fā)最佳實(shí)踐,以避免常見的陷阱。仔細(xì)考慮可用的工具,以確保它們與你的需求和文化兼容。
測(cè)試
有許多類型的測(cè)試,例如:?jiǎn)卧獪y(cè)試,系統(tǒng)集成測(cè)試,性能測(cè)試,***測(cè)試。在開發(fā)階段,大多數(shù)測(cè)試由開發(fā)人員或測(cè)試人員編寫,以驗(yàn)證應(yīng)用程序是否滿足其要求。
測(cè)試僅在它們運(yùn)行正確的代碼時(shí)有效。在實(shí)現(xiàn)功能和測(cè)試的同時(shí),保持開發(fā)速度是具有挑戰(zhàn)性的。
開發(fā)最佳實(shí)踐
投入時(shí)間來識(shí)別和配置工具,以便找到你關(guān)心的代碼問題,無需為開發(fā)人員帶來更多的工作。經(jīng)常自動(dòng)運(yùn)行分析工具和測(cè)試,以確保開發(fā)人員在代碼剛寫完不久,就能定位到問題。
盡快地定位到所有的診斷輸出 - 無論是編譯器警告,標(biāo)準(zhǔn)違例,通過靜態(tài)分析識(shí)別的缺陷,還是測(cè)試失敗。如果新的診斷全部是可忽略的,那么審查所起的作用就增加了,開發(fā)人員也不必再為代碼問題煩惱。
采用這些最佳實(shí)踐有助于提高代碼的質(zhì)量,安全性和可維護(hù)性,開發(fā)人員的一致性和生產(chǎn)力以及發(fā)布的可預(yù)測(cè)性。
關(guān)心 | 工具 | 影響 |
一致性,可維護(hù)性 | 編碼標(biāo)準(zhǔn),靜態(tài)分析,代碼審查 | 一致的間距,命名和格式化提高了可讀性,并使開發(fā)人員更容易編寫和維護(hù)代碼。 |
正確性 | 代碼審查,靜態(tài)分析,動(dòng)態(tài)分析,測(cè)試 | 代碼不僅需要在語法上有效,而且必須按照開發(fā)人員的意圖并滿足項(xiàng)目需求。 |
功能 | 測(cè)試 | 測(cè)試驗(yàn)證代碼是否滿足要求,如正確性,可擴(kuò)展性,魯棒性和安全性。 |
安全 | 編碼標(biāo)準(zhǔn),代碼審查,靜態(tài)分析,動(dòng)態(tài)分析,測(cè)試 | 安全是一個(gè)非常復(fù)雜的問題; 任何弱點(diǎn)或缺陷都可能被利用。 |
開發(fā)人員生產(chǎn)力 | 編碼標(biāo)準(zhǔn),靜態(tài)分析,測(cè)試 | 當(dāng)他們有工具來識(shí)別錯(cuò)誤時(shí),開發(fā)人員更快地實(shí)現(xiàn)代碼更改。 |
釋放可預(yù)測(cè)性 | 編碼標(biāo)準(zhǔn),代碼審查,靜態(tài)分析,動(dòng)態(tài)分析,測(cè)試 | 簡(jiǎn)化后期活動(dòng),盡早解決缺陷和問題,盡可能縮短修復(fù)周期。 |
C#的一個(gè)主要優(yōu)勢(shì)是其靈活的類型系統(tǒng); 類型安全有助于早期發(fā)現(xiàn)錯(cuò)誤。通過強(qiáng)制實(shí)施嚴(yán)格的類型規(guī)則,編譯器能夠幫助你保持正確的編碼實(shí)踐。C#語言和.NET框架提供了豐富的類型集合以適應(yīng)最常見的需求。大多數(shù)開發(fā)人員很好地了解常見的類型及其用途,但有一些常見的誤解和誤用。
有關(guān).NET Framework類庫(kù)的更多信息可以在MSDN庫(kù)中找到。
了解和使用標(biāo)準(zhǔn)接口
某些接口涉及常用的C#特性。例如,IDisposable允許使用常用的資源處理習(xí)語,例如“using”關(guān)鍵字。理解什么時(shí)候使用接口,能夠使你編寫更容易維護(hù)的C#代碼。
避免ICloneable - 設(shè)計(jì)者從來沒有明確拷貝的對(duì)象是深拷貝還是淺拷貝。由于沒有正確拷貝對(duì)象行為的標(biāo)準(zhǔn),也就無法有效的使用這樣的接口。
結(jié)構(gòu)
盡量避免寫到結(jié)構(gòu)體。將它們視為不可變的,能夠防止混淆的發(fā)生,并且在共享內(nèi)存的場(chǎng)景(如多線程應(yīng)用程序)下更安全。相反,在創(chuàng)建結(jié)構(gòu)體時(shí)使用初始化對(duì)象,如果需要更改值,則創(chuàng)建新的實(shí)例。
要了解哪些標(biāo)準(zhǔn)類型/方法是不可變的并返回新值(例如,string,DateTime)和哪些是可變的(List.Enumerator)。
字符串
字符串可以為null,因此在適當(dāng)時(shí),使用起來很方便。等價(jià)(s.Length == 0)可能會(huì)拋出一個(gè)NullReferenceException,但是String.IsNullOrEmpty(s)和String.IsNullOrWhitespace(s)函數(shù)能夠優(yōu)雅地處理null。
標(biāo)記枚舉
枚舉類型和常量值是能表露出自己含義的標(biāo)識(shí)符,用于替換魔術(shù)數(shù)字,以便使得代碼更加可讀。
如果你發(fā)現(xiàn)需要?jiǎng)?chuàng)建枚舉的集合,標(biāo)記枚舉可能是一個(gè)更簡(jiǎn)單的選擇:
[Flag]public enum Tag { None =0x0, Tip =0x1, Example=0x2}
這使你能夠輕松地為代碼段添加多個(gè)標(biāo)簽:
snippet.Tag = Tag.Tip | Tag.Example
這可以改善數(shù)據(jù)封裝,因?yàn)槟悴槐負(fù)?dān)心通過Tag property getter暴露內(nèi)部集合。
等價(jià)比較
有兩種類型的等價(jià):
引用相等,這意味著兩個(gè)引用,引用了同一個(gè)對(duì)象。
值平等,這意味著兩個(gè)不同的對(duì)象是等值的。
此外,C#提供了多種方法來測(cè)試等價(jià)。最常見的方法是使用:
==和!=運(yùn)算符
繼承自O(shè)bject的虛擬Equals方法
靜態(tài)Object.Equals方法
IEquatable接口的Equals方法
靜態(tài)Object.ReferenceEquals方法
可能難以知道預(yù)期的是引用相等還是值相等。如果你重寫Equals,不要忘記IEquatable <T>,GetHashCode(),如MSDN中所述。
注意無類型容器對(duì)重載的影響??紤]比較“myArrayList [0] == myString”。數(shù)組列表元素是編譯時(shí)類型“對(duì)象”,因此使用引用等價(jià)。C#編譯器會(huì)警告你這個(gè)潛在錯(cuò)誤,但是有許多類似的情況,編譯器不會(huì)對(duì)意外的引用相等發(fā)出警告。
封裝你的數(shù)據(jù)
類負(fù)責(zé)正確地管理數(shù)據(jù)。出于性能原因,它們通常緩存部分結(jié)果或者對(duì)其內(nèi)部數(shù)據(jù)的一致性做出假設(shè)。數(shù)據(jù)公開訪問會(huì)影響你緩存或做出假設(shè)的能力,對(duì)性能,安全性和并發(fā)性都有潛在影響。例如,暴露可變成員,如通用集合和數(shù)組,允許用戶在你不知情的情況下修改這些結(jié)構(gòu)。
屬性
屬性使你能夠精確控制用戶如何與你的對(duì)象進(jìn)行交互,除了你通過訪問修改器控制的之外。具體來說,屬性使你能夠控制讀取和寫入時(shí)發(fā)生的情況。
屬性使你能夠建立穩(wěn)定的API,同時(shí)重寫getter和setter中的數(shù)據(jù)訪問邏輯,或提供數(shù)據(jù)綁定源。
不要也不要讓屬性獲取器拋出異常,避免修改對(duì)象狀態(tài)。這樣就意味著需要一種方法而不是屬性獲取器。
有關(guān)屬性的詳細(xì)信息,請(qǐng)參閱MSDN的屬性設(shè)計(jì)主題:http : //msdn.microsoft.com/en-us/library/ms229006(v=vs.120).aspx
仔細(xì)的使用getters,因?yàn)樗懈弊饔?。開發(fā)者認(rèn)為成員訪問是一個(gè)微不足道的操作,所以他們經(jīng)常忘記在代碼審查期間考慮帶來的副作用。
對(duì)象初始化
你可以在創(chuàng)建表達(dá)式時(shí),對(duì)新創(chuàng)建的對(duì)象設(shè)置屬性。使用特定值來創(chuàng)建Class Cde 對(duì)象,并用到Foo和Bar屬性:
new C {Foo=blah, Bar=blam}
你還可以使用特定的屬性名稱,創(chuàng)建匿名類型的實(shí)例:
var myAwesomeObject = new {Name=”Foo”, Size=10};
初始化會(huì)在構(gòu)造主體運(yùn)行之前執(zhí)行,確保在進(jìn)入構(gòu)造器之前字段已經(jīng)初始化了。因?yàn)闃?gòu)造函數(shù)還沒有運(yùn)行,所以字段初始化器不能以任何方式引用“this”。
過度指定輸入?yún)?shù)
為了幫助防止特定方法的過度使用,請(qǐng)嘗試采用方法所需的最小特定類型。例如,考慮一個(gè)迭代List <Bar>的方法:
public void Foo(List<Bar> bars) { foreach(var b in bars) { // do something with the bar... } }
對(duì)于其他的IEnumerable <Bar>集合,這段代碼能夠很好地運(yùn)行,但是通過為參數(shù)指定List <Bar>,你就需要集合必須是一個(gè)List。選擇參數(shù)的最小特定類型(IEnumerable <T>,ICollection <T>等),以確保方法的最大有用性
泛型是一種十分有效的方式,來定義與類型無關(guān)的結(jié)構(gòu)體和確保類型安全的算法。
使用諸如List <T>之類的泛型集合,而不是無類型的集合如ArrayList,能夠提高類型的安全性和性能。
當(dāng)實(shí)現(xiàn)泛型類型時(shí),可以使用“default”關(guān)鍵字來獲取那種無法硬編碼到實(shí)現(xiàn)中的默認(rèn)值。具體來說就是,數(shù)字類型的默認(rèn)值為0; 引用和可空值類型的默認(rèn)值為null。
T t = default(T);
有兩種類型的conversions轉(zhuǎn)化。顯式轉(zhuǎn)換必須由開發(fā)人員調(diào)用,隱式轉(zhuǎn)換由編譯器基于上下文來應(yīng)用。
Cast | 描述 |
Tree tree =(Tree)obj; | 如果obj是tree類型時(shí),請(qǐng)使用這個(gè)。如果obj不是Tree類型,將產(chǎn)生一個(gè)InvalidCast異常。 |
Tree tree = obj as Tree; | 當(dāng)你無法確定obj是否是Tree類型時(shí),請(qǐng)使用這個(gè)。如果obj不是Tree類型,將會(huì)給Tree分配一個(gè)空值。在必要時(shí),請(qǐng)使用這種轉(zhuǎn)換方式,因?yàn)樗枰獙?duì)返回值進(jìn)行條件處理。這些額外的代碼可能產(chǎn)生更多的錯(cuò)誤,使得代碼更難以讀取和調(diào)試。 |
類型轉(zhuǎn)化時(shí),經(jīng)常會(huì)遇到以下兩種情形:
表達(dá)式的運(yùn)行時(shí)類型比編譯器能推斷出的類型更加具體。轉(zhuǎn)換指示編譯器將表達(dá)式當(dāng)做更具體的類型來處理。如果你的假設(shè)不正確,編譯器將拋出異常的代碼。例如,從對(duì)象到字符串的轉(zhuǎn)換。
轉(zhuǎn)換指示編譯器會(huì)生成關(guān)聯(lián)表達(dá)式的值的代碼,如果沒有生成,則會(huì)拋出異常。例如,從double到integer的轉(zhuǎn)換。
兩種類型轉(zhuǎn)換都是很危險(xiǎn)的。第一種類型的轉(zhuǎn)換提出了一個(gè)問題,“為什么開發(fā)人員知道,而編譯器不知道?”如果在這種情況下,嘗試更改程序,以便編譯器可以成功地推導(dǎo)出正確的類型。如果你認(rèn)為一個(gè)對(duì)象的運(yùn)行時(shí)類型可能比編譯時(shí)類型更具體,那么你可以使用“is”或“as”運(yùn)算符。
第二種類型轉(zhuǎn)換引發(fā)了一個(gè)問題,“為什么操作是在開始的地方執(zhí)行的,而不是在目標(biāo)數(shù)據(jù)類型?”如果你需要一個(gè)int類型的結(jié)果,使用int比double更有意義。
有關(guān)其他想法,請(qǐng)參閱:http://blogs.msdn.com/b/ericlippert/archive/tags/cast+operator/
在顯式轉(zhuǎn)換是正確的操作情況下,通過使用適當(dāng)?shù)倪\(yùn)算符來提高可讀性,調(diào)試能力和可測(cè)試性。
異常不是條件
異常通常不應(yīng)用于控制程序流; 它們代表的是,在運(yùn)行時(shí)你可能無法恢復(fù)的意外情況。如果你預(yù)期你應(yīng)該處理的情況,主動(dòng)檢查情況,而不是等待異常發(fā)生。
要將格式不正確的字符串正常轉(zhuǎn)換為數(shù)字,請(qǐng)使用TryParse()方法; 而不是拋出異常,它返回一個(gè)布爾值,指示解析是否成功。
使用異常處理范圍
在catch內(nèi)部寫代碼,并且仔細(xì)處理成程序塊。已執(zhí)行過的代碼已經(jīng)不存在這些異常。例如:
Frobber originalFrobber = null;try { originalFrobber = this.GetCurrentFrobber(); this.UseTemporaryFrobber(); this.frobSomeBlobs(); }finally { this.ResetFrobber(originalFrobber); }
如果GetCurrentFrobber()拋出異常,那么當(dāng)finally block被執(zhí)行時(shí),originalFrobber仍然為null;
明智的處理異常
只捕獲你準(zhǔn)備處理的特定異常,并且只針對(duì)特定代碼段。除非你的意圖只是簡(jiǎn)單的記錄并重新拋出異常。某些例外可能使應(yīng)用程序處于一種狀態(tài),那么就需要避免處理所有異?;蚋惍惓5膶?shí)例。最好是在沒有進(jìn)一步損壞的情況下應(yīng)用已經(jīng)崩潰,而不是試圖恢復(fù)并造成損害。你的恢復(fù)嘗試可能會(huì)在不經(jīng)意間使事情更糟。
處理致命異常有一些細(xì)微差別,特別是關(guān)于finally block執(zhí)行時(shí),如何影響異常安全和調(diào)試器。有關(guān)詳情,請(qǐng)參閱:http : //incrediblejourneysintotheknown.blogspot.com/2009/02/fatal-exceptions-and-why-vbnet-has.html
使用最高級(jí)異常處理,來安全到處理程序的意外情況并公開信息以幫助調(diào)試問題。請(qǐng)謹(jǐn)慎使用catch塊來解決本可以安全處理的特定情況,為無法預(yù)料的異常預(yù)留最高級(jí)的處理。
如果你捕獲到一個(gè)異常,那么就需要采取一些措施來處理。不計(jì)其它后果地處理當(dāng)前異常只會(huì)使問題難以識(shí)別和調(diào)試。
對(duì)于公開了工作API的代碼來說,將異常包含于自定義異常中,是特別有用的。異常是方法的可見接口的一部分,應(yīng)該與參數(shù)和返回值一起被控制??赡軐?dǎo)致更多異常的方法,是不應(yīng)該被使用在可維護(hù)解決方案中的。
拋出和重新拋出異常
當(dāng)你希望在更深層次處理一個(gè)捕獲到的異常時(shí),維護(hù)原始異常狀態(tài)和堆棧對(duì)于調(diào)試有極大的幫助。需要仔細(xì)地平衡,調(diào)試和安全注意事項(xiàng)。
簡(jiǎn)單的重新拋出異常也是一個(gè)好選擇:
throw;
或者在新的throw中使用異常作為InnerException:
throw new CustomException(...,ex);
不要顯式地重新拋出捕獲的異常,如下所示:
throw e;
這將復(fù)位異常狀態(tài)到當(dāng)前行,并且阻止調(diào)試。
一些異常發(fā)生在代碼的上下文之外。對(duì)于這些情況,你可能需要添加事件的處理程序,如ThreadException或UnhandledException,而不是使用catch塊。例如,表單處理程序線程的上下文中引發(fā)的Windows窗體異常。
數(shù)據(jù)完整性
異常不得影響數(shù)據(jù)模型的完整性。你需要確保你的對(duì)象處于一致的狀態(tài) - 不會(huì)違反類實(shí)現(xiàn)所做的任何假設(shè)。否則,通過“恢復(fù)”,你只能使你的代碼變得混亂,之后還會(huì)導(dǎo)致進(jìn)一步的損害。
事件和代理相互協(xié)助,當(dāng)事件發(fā)生時(shí),為類提供了一種方法來通知用戶。事件類似于委托類型的字段; 當(dāng)創(chuàng)建對(duì)象時(shí),它們將自動(dòng)初始化為null。
事件的值是一個(gè)多級(jí)代理。也就是一個(gè)可以依次調(diào)用其他代理的代理。你可以為事件分配委托; 可以通過+ =和 - =等操作符操作事件。
注意競(jìng)逐條件
如果事件在線程之間共享,則有可能在你檢查null之后并且在調(diào)用它之前,另一個(gè)線程將刪除所有參數(shù) – 就會(huì)拋出NullReferenceException異常。
標(biāo)準(zhǔn)解決方案是創(chuàng)建事件的本地副本,用于測(cè)試和調(diào)用。你仍然需要小心,在其他線程中刪除的任何參數(shù),在他們的委托被意外調(diào)用時(shí)會(huì)正常運(yùn)行。你還可以實(shí)施鎖定,以一種能夠避免問題的方式為操作排隊(duì)列。
public event EventHandler SomethingHappened;private void OnSomethingHappened() { // The event is null until somebody hooks up to it // Create our own copy of the event to protect against another thread removing our subscribers EventHandler handler = SomethingHappened; if (handler != null) handler(this,new EventArgs()); }
更多關(guān)于時(shí)間和競(jìng)逐的信息,請(qǐng)參閱:http : //blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx
屬性提供了一種方法,用于將組件,類和屬性的元數(shù)據(jù)與其屬性的信息一起輸入。它們通常用于向代碼用戶提供信息,如代碼調(diào)試器,測(cè)試框架和應(yīng)用程序。你可以定義自己使用的屬性,也可以使用表中列出的預(yù)定義屬性。
屬性 | 使用 | 目的 |
Debugger顯示 | 調(diào)試器 | 調(diào)試器顯示格式 |
InternalsVisibleTo | 會(huì)員訪問 | 能將內(nèi)部成員暴露給特定的其他類。使用它,測(cè)試?yán)炭梢栽L問受保護(hù)的成員。 |
默認(rèn)值 | 屬性 | 指定屬性的默認(rèn)值。 |
小心使用DebuggerStepThrough屬性,如果應(yīng)用了這個(gè)屬性,會(huì)導(dǎo)致很難在方法中找到bug,因?yàn)槟悴荒軉尾綀?zhí)行或打斷它們!
調(diào)試是任何開發(fā)工作中重要的組成部分。除了提供對(duì)運(yùn)行時(shí)環(huán)境的常規(guī)不透明方面的可見性之外,調(diào)試器可以進(jìn)入運(yùn)行時(shí)環(huán)境,同時(shí)調(diào)試器還會(huì)導(dǎo)致應(yīng)用程序的在沒有調(diào)試器的情況下,獲的不同的結(jié)果。
使異常堆??梢?/p>
要查看當(dāng)前框架的異常狀態(tài),可以在Visual Studio Watch窗口中添加表達(dá)式“$ exception”。此變量包含當(dāng)前異常狀態(tài),類似于你在catch塊中看到的情況,除非你可以在調(diào)試器中看到異常狀態(tài),否則就不必在代碼中實(shí)際捕獲異常。
注意訪問器中的副作用
如果你所使用的屬性有副作用,請(qǐng)考慮是否應(yīng)使用屬性或調(diào)試器設(shè)置,來防止調(diào)試器自動(dòng)調(diào)用getter。例如,你的類可能具有這些屬性:
private int remainingAccesses = 10;private string meteredData;public string MeteredData { get { if (remainingAccesses-- > 0) return meteredData; return null; } }
第一次在調(diào)試器中查看此對(duì)象時(shí),remainingAccesses將顯示為值10,MeteredData顯示為null。如果你將鼠標(biāo)懸停在remainingAccesses上,你會(huì)看到它的值現(xiàn)在是9。調(diào)試器顯示的屬性值已經(jīng)改變了對(duì)象的狀態(tài)。
早做計(jì)劃,經(jīng)常衡量,然后優(yōu)化
在設(shè)計(jì)期間設(shè)置合理的性能目標(biāo)。在開發(fā)期間,專注于正確性而不是細(xì)微優(yōu)化。經(jīng)常根據(jù)目標(biāo)衡量你的效果。如果你沒有達(dá)到目標(biāo),則應(yīng)該花費(fèi)時(shí)間來優(yōu)化程序。
始終采用最合適的工具,在具有可重復(fù)性和盡可能接近用戶所經(jīng)歷的實(shí)際條件的情況下,對(duì)性能進(jìn)行經(jīng)驗(yàn)性測(cè)量。
由于CLR優(yōu)化,有時(shí)效率低下的代碼實(shí)際上比高效的代碼運(yùn)行速度更快。例如,CLR優(yōu)化覆蓋了整個(gè)數(shù)組的循環(huán),以避免隱式的單元范圍檢查。開發(fā)人員通常在循環(huán)數(shù)組之前計(jì)算長(zhǎng)度:
int[] a_val = int[4000];int len = a_val.Length;for (int i = 0; i < len; i++) a_val[i] = i;
通過將長(zhǎng)度放在變量中,CLR可能無法識(shí)別模式,并將跳過優(yōu)化。手動(dòng)優(yōu)化違反了直覺,會(huì)導(dǎo)致性能較差。
構(gòu)建字符串
如果你要做很多字符串連接,應(yīng)該使用System.Text.StringBuilder對(duì)象,這樣可以避免構(gòu)建許多臨時(shí)字符串對(duì)象。
對(duì)集合使用批處理操作
如果需要?jiǎng)?chuàng)建和填充已知大小的集合,請(qǐng)?jiān)趧?chuàng)建集合時(shí)保留空間,以避免由于重復(fù)重新分配而導(dǎo)致的性能和資源問題。你可以使用AddRange方法(如List <T>中的方法)進(jìn)一步提高性能:
Persons.AddRange(listBox.Items);
垃圾回收器能夠自動(dòng)清理內(nèi)存。即使如此,所有一次性資源,也必須妥善處理 - 特別是那些不由垃圾收集器管理的資源。
資源管理問題的常見來源 | |
內(nèi)存碎片 | 如果沒有足夠大的連續(xù)塊的虛擬地址空間,分配將失敗。 |
過程限制 | 進(jìn)程通常訪問系統(tǒng)可用的內(nèi)存和資源的嚴(yán)格子集。 |
資源泄漏 | 垃圾回收器只管理內(nèi)存。其他資源需要由應(yīng)用程序正確管理。 |
資源困境 | 依賴于垃圾收集器和終結(jié)器的資源在不再使用時(shí),不會(huì)變得立即可用。事實(shí)上,它們可能永遠(yuǎn)不可用。 |
使用try / finally塊來確保資源正確釋放,或讓你的類實(shí)現(xiàn)IDisposable,并利用更清潔和更安全的using語句。
using (StreamReader reader=new StreamReader(file)) { //your code here
避免代碼中使用垃圾收集器
盡量不要通過調(diào)用GC.Collect()干擾垃圾收集器,而應(yīng)該將重點(diǎn)放在正確釋放或處置資源。當(dāng)測(cè)量性能時(shí),如果你能夠正確的評(píng)估影響,在小心的讓垃圾收集器運(yùn)行。
避免編寫終結(jié)器
不同于最流行的錯(cuò)誤認(rèn)知,你的類不需要一個(gè)Finalizer,僅僅是因?yàn)樗鼘?shí)現(xiàn)IDisposable!你可以實(shí)現(xiàn)IDisposable以使你的類能夠在任何所有的復(fù)合實(shí)例上調(diào)用Dispose,但是終結(jié)器只應(yīng)在直接擁有非托管資源的類上實(shí)現(xiàn)。
Finalizer主要用于調(diào)用interop API,來處理Win32句柄,SafeHandle更容易處理。
你不能推測(cè)你的終結(jié)器 - 它總是在終結(jié)器線程上運(yùn)行 - 可以安全地與其他對(duì)象交互。那些其他對(duì)象本身可能已經(jīng)完成了。
并發(fā)和多線程編程是一件很復(fù)雜和困難的事情。在向應(yīng)用程序添加并發(fā)之前,請(qǐng)確保你真正了解自己正在做什么 - 有很多細(xì)微之處需要了解!
多線程應(yīng)用程序非常難以推理,并且容易受到諸如通常不影響單線程應(yīng)用程序的競(jìng)爭(zhēng)條件和死鎖等問題的影響。鑒于這些風(fēng)險(xiǎn),你應(yīng)該最后才考慮多線程。如果你必須使用多個(gè)線程,請(qǐng)盡量通過不在線程之間共享內(nèi)存來最小化同步的需要。如果必須同步線程,請(qǐng)使用最高級(jí)別的同步機(jī)制。
最高級(jí)別,這些機(jī)制包括:
Async-await/Task Parallel Library/Lazy<T>
Lock/monitor/AutoResetEvent
Interlocked/Semaphore
Volatile fields and explicit barriers
C#/ .NET中并發(fā)的復(fù)雜性很難就在這里解釋清楚。如果你想要或需要開發(fā)一個(gè)利用并發(fā)的應(yīng)用程序,請(qǐng)查看詳細(xì)的文檔,如O'Reilly的“Concurrency in C# Cookbook”。
使用volatile
將字段標(biāo)記為“易變”是高級(jí)功能,即使專家也經(jīng)常誤解。C#編譯器將確保訪問字段具有獲取和釋放語義; 這不同于確保對(duì)該字段的所有訪問都處于鎖定狀態(tài)。如果你不知道什么是獲取和釋放語義,以及它們?nèi)绾斡绊慍PU級(jí)優(yōu)化,則應(yīng)避免使用volatile字段。相反,應(yīng)該使用較高級(jí)別的工具,如任務(wù)并行庫(kù)或CancellationToken類型。
利用線程安全的內(nèi)置方法
標(biāo)準(zhǔn)庫(kù)類型通常提供方便線程安全訪問對(duì)象的方法。例如,Dictionary.TryGetValue()。使用這些方法通常使你的代碼更清潔,你不需要擔(dān)心如TOCTTOU or TOCTOU場(chǎng)景等數(shù)據(jù)競(jìng)爭(zhēng)的情況。
不要鎖定“this”,字符串或其他常見的公共對(duì)象
當(dāng)實(shí)現(xiàn)在多線程上下文中使用的類時(shí),要非常小心使用鎖。鎖定此字符串或其他公共對(duì)象,會(huì)阻止封裝鎖定狀態(tài),并可能導(dǎo)致死鎖。你需要防止其他代碼鎖定你的實(shí)現(xiàn)上正在使用的對(duì)象; 最安全的做法是使用一個(gè)私人的對(duì)象成員。
引用null
不適當(dāng)?shù)氖褂胣ull,是編碼缺陷的常見來源,可能會(huì)導(dǎo)致程序崩潰和其它意外行為。如果你嘗試訪問一個(gè)空引用,以為它是一個(gè)對(duì)象的有效引用一樣 - 例如,通過訪問一個(gè)屬性或方法,運(yùn)行時(shí)將拋出一NullReferenceException異常。
靜態(tài)和動(dòng)態(tài)分析工具可以幫助你在發(fā)布代碼之前識(shí)別潛在的NullReferenceException異常。在C#中,空引用通常由尚未引用對(duì)象的變量引起。對(duì)于空值類型和引用類型來說,Null是一個(gè)有效值。例如,Nullable <Int>,空委托,取消訂閱事件,會(huì)在“as”轉(zhuǎn)換,以及在許多其他情況下失敗。
每個(gè)空引用異常是都一個(gè)錯(cuò)誤。不應(yīng)該去捕獲NullReferenceException,而應(yīng)該嘗試在使用它們之前測(cè)試對(duì)象是否為null。這也使得代碼更容易被最小化try / catch塊讀取。
從數(shù)據(jù)庫(kù)表中讀取數(shù)據(jù)時(shí),請(qǐng)確保,缺失值可以表示為DBNull對(duì)象,而不是空引用。不要指望它們表現(xiàn)的像潛在的空引用。
將十進(jìn)制值替換為二進(jìn)制數(shù)
浮點(diǎn)數(shù)和雙精度表示二進(jìn)制有理數(shù),不是小數(shù)有理數(shù),在存儲(chǔ)十進(jìn)制值時(shí)必須使用二進(jìn)制的近似值。從十進(jìn)制的角度來看,這些二進(jìn)制近似具有不一致的舍入和精度 - 有時(shí)導(dǎo)致算術(shù)運(yùn)算的意外結(jié)果。因?yàn)楦↑c(diǎn)運(yùn)算通常在硬件中執(zhí)行,硬件條件可能會(huì)不可預(yù)測(cè)地加劇這些差異。
當(dāng)小數(shù)精度非常重要時(shí),使用十進(jìn)制,就像財(cái)務(wù)計(jì)算等情況。
修改結(jié)構(gòu)
一個(gè)常見的錯(cuò)誤情況是忘記結(jié)構(gòu)體是值類型的,這就意味著它們被復(fù)制了并且通過值來進(jìn)行傳遞。假設(shè)你有這樣的代碼:
struct P { public int x; public int y; }void M() { P p = whatever; … p.x = something; … N(p);
有一天,維護(hù)者決定將代碼重構(gòu)為:
void M() { P p = whatever; Helper(p); N(p); }void Helper(P p) { … p.x = something;
現(xiàn)在當(dāng)在M()中調(diào)用N(p)時(shí),p具有錯(cuò)誤的值。調(diào)用助手(p)傳遞p的副本,而不是p的引用,因此Helper()中執(zhí)行的變化將丟失。相反,Helper會(huì)返回修改的p的副本。
意外的算術(shù)
C#編譯器保護(hù)你出現(xiàn)常量的算術(shù)溢出,但不一定是計(jì)算值。
忽略保存返回值
與結(jié)構(gòu)體不同,類是引用類型,方法可以修改引用的對(duì)象。然而,不是所有的對(duì)象方法都實(shí)際修改了引用的對(duì)象,一些會(huì)返回一個(gè)新對(duì)象。當(dāng)開發(fā)人員調(diào)用后者時(shí),他們需要記住將返回值賦給變量,以便使用修改后的對(duì)象。在代碼審查期間,這種類型的問題通常在會(huì)被發(fā)現(xiàn)。一些對(duì)象,如字符串,是不可變的,所以方法從不修改這些對(duì)象。即使如此,開發(fā)人員也會(huì)通常忘記。
例如,考慮string.Replace():
string label = “My name is Aloysius”; label.Replace(“Aloysius”, “secret”);
代碼打印“我的名稱是Aloysius”,因?yàn)镽eplace方法不修改字符串。
不要使迭代器/枚舉器變得無效
不要在迭代時(shí)修改集合。
List<Int> myItems = new List<Int>{20,25,9,14,50};foreach(int item in myItems) { if (item < 10) { myItems.Remove(item); // iterator is now invalid! // you’ll get an exception on the next iteration
如果你運(yùn)行這個(gè)代碼,一旦循環(huán)到集合中的下一個(gè)項(xiàng)目時(shí)。你會(huì)收到一個(gè)異常拋出。
正確的解決方案,是使用第二個(gè)列表來保存你要?jiǎng)h除的項(xiàng)目,然后在刪除時(shí)迭代該列表:
List<Int> myItems = new List<Int>{20,25,9,14,50}; List<Int> toRemove = new List<Int>();foreach(int item in myItems) { if (item < 10) { toRemove.Add(item); } }foreach(int item in toRemove) {
或者如果你使用C#3.0或更高版本,你可以使用List <T> .RemoveAll。
就像這樣:
myInts.RemoveAll(item => (item < 10));
屬性名稱錯(cuò)誤
在實(shí)現(xiàn)屬性時(shí),請(qǐng)注意屬性名稱不同于類中使用的數(shù)據(jù)成員。在訪問屬性時(shí),容易意外使用相同的名稱,并導(dǎo)致出現(xiàn)無限遞歸的情況。
// The following code will trigger infinite recursion private string name;public string Name { get { return Name; // should reference “name” instead.
當(dāng)重命名間接屬性時(shí)要小心。例如,WPF中的數(shù)據(jù)綁定,會(huì)將屬性名稱指定為字符串。如果不小心更改該屬性名稱,你將會(huì)無意中創(chuàng)建了一個(gè)編譯器無法防護(hù)的問題。
以上就是所有C#開發(fā)人員應(yīng)該知道的13件事情。
了解了C#開發(fā)中應(yīng)該知道的13件事情,有助于我們更好地使用C#進(jìn)行開發(fā),當(dāng)然在開發(fā)時(shí),也可以借助一些使用C#編寫的開發(fā)工具。如ComponentOne Studio Enterprise,這是一款專注于企業(yè)應(yīng)用的.NET全功能控件套包,支持WinForms、WPF、UWP、ASP.NET MVC等多個(gè)平臺(tái),幫助、在縮減成本的同時(shí),提前交付豐富的桌面、Web和移動(dòng)企業(yè)應(yīng)用。
原文鏈接:https://dzone.com/refcardz/csharp
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。