C#編程中最常見的10個錯誤
原文來自互聯(lián)網(wǎng),由長沙DotNET技術(shù)社區(qū)編譯。如譯文侵犯您的署名權(quán)或版權(quán),請聯(lián)系小編,小編將在24小時內(nèi)刪除。限于譯者的能力有限,個別語句翻譯略顯生硬,還請見諒。
原文來自:https://www.toptal.com/c-sharp/top-10-mistakes-that-c-sharp-programmers-make
作者簡介:
帕特里克·賴德(PATRICK RYDER)在Microsoft工作期間幫助創(chuàng)建了VB 1.0和更高版本的.NET平臺。自2000年以來,他專注于全棧開發(fā)。
C#是針對Microsoft 公共語言運行庫(CLR)的幾種語言之一。面向CLR的語言受益于多種功能,例如跨語言集成和異常處理,增強的安全性,簡化的組件交互模型以及調(diào)試和性能分析服務(wù)。在當(dāng)今的CLR語言中,C#被廣泛用于針對Windows臺式機,移動或服務(wù)器環(huán)境的復(fù)雜,專業(yè)的開發(fā)項目中。(譯者注,目前已支持各類跨平臺的操作系統(tǒng)環(huán)境)
C#是一種面向?qū)ο蟮膹婎愋驼Z言。在編譯和運行時,C#中嚴(yán)格的類型檢查會導(dǎo)致盡早報告大多數(shù)典型的C#編程錯誤,并準(zhǔn)確定位其位置。這可以在C Sharp編程中節(jié)省大量時間,相比之下,在更自由地執(zhí)行類型安全的語言中,跟蹤令人困惑的錯誤的原因可能會在違規(guī)操作發(fā)生很久之后才發(fā)生。但是,許多C#編碼人員無意間(或不小心)放棄了這種檢測的好處,這導(dǎo)致了本C#教程中討論的一些問題。
關(guān)于本C Sharp編程教程
本教程描述了C#程序員犯下的10種最常見的C#編程錯誤或應(yīng)避免的問題,并為他們提供了幫助。
盡管本文中討論的大多數(shù)錯誤都是C#特定的,但有些錯誤也與其他以CLR為目標(biāo)或使用框架類庫(FCL)的語言有關(guān)。
常見的C#編程錯誤#1:使用值類型與引用相等,或反過來
C ++和許多其他語言的程序員習(xí)慣于控制他們分配給變量的值是簡單的值還是對現(xiàn)有對象的引用。但是,在C Sharp編程中,該決定由編寫對象的程序員決定,而不是由實例化該對象并將其分配給變量的程序員做出。對于那些試圖學(xué)習(xí)C#編程的人來說,這是一個常見的“陷阱”。
如果您不知道所使用的對象是值類型還是引用類型,則可能會遇到一些意外。例如:
Point point1 = new Point(20, 30);Point point2 = point1;point2.X = 50;Console.WriteLine(point1.X); // 20 (does this surprise you?)Console.WriteLine(point2.X); // 50Pen pen1 = new Pen(Color.Black);Pen pen2 = pen1;pen2.Color = Color.Blue;Console.WriteLine(pen1.Color); // Blue (or does this surprise you?)Console.WriteLine(pen2.Color); // Blue
正如你所看到的,Point和Pen對象創(chuàng)建方式不盡相同,但值point1保持不變,當(dāng)一個新的X坐標(biāo)值被分配到point2,一個新的顏色被分配到修改pen2。因此,我們可以推斷出,point1并且point2每個Point對象都包含自己的對象副本,而pen1和pen2都包含對同一Pen對象的引用。
但是,如果不進(jìn)行此實驗,我們怎么知道呢?
答案是查看對象類型的定義(您可以在Visual Studio中通過將光標(biāo)置于對象類型的名稱上并按F12輕松地完成此操作):
public struct Point { ... } // defines a “value” typepublic class Pen { ... } // defines a “reference” type
如上所示,在C#編程中,struct關(guān)鍵字用于定義值類型,而class關(guān)鍵字用于定義引用類型。
對于那些具有C ++背景的人,由于C ++和C#關(guān)鍵字之間的許多相似之處而陷入一種錯誤的安全感,這種行為可能會讓人感到意外,您可能會從C#教程中尋求幫助。
如果您要依賴值和引用類型之間不同的某些行為(例如,將對象作為方法參數(shù)傳遞并讓該方法更改對象狀態(tài)的能力),請確保您正在處理正確的對象類型,以避免C#編程問題。
常見的C#編程錯誤#2:誤解了未初始化變量的默認(rèn)值
在C#中,值類型不能為null。根據(jù)定義,值類型具有值,甚至值類型的未初始化變量也必須具有值。這稱為該類型的默認(rèn)值。當(dāng)檢查變量是否未初始化時,這會導(dǎo)致以下結(jié)果,通常是意外的結(jié)果:
class Program {static Point point1;static Pen pen1;static void Main(string[] args) {Console.WriteLine(pen1 == null); // TrueConsole.WriteLine(point1 == null); // False (huh?)}}
為什么point1不為空?答案是Point是值類型,它的默認(rèn)值為Point(0,0),而不是null。未能意識到這一點是在C#中非常容易(也是常見)的錯誤。許多(但不是全部)值類型都有一個IsEmpty屬性,您可以檢查該屬性是否等于其默認(rèn)值:
Console.WriteLine(point1.IsEmpty); // True當(dāng)您檢查變量是否已初始化時,請確保您知道該類型的未初始化變量在默認(rèn)情況下將具有什么值,并且不要依賴于它為null。
常見的C#編程錯誤#3:使用不正確或未指定的字符串比較方法
比較C#中的字符串有很多不同的方法。
盡管許多程序員使用==運算符進(jìn)行字符串比較,但這實際上是最不希望采用的方法之一,主要是因為它沒有在代碼中明確指定需要哪種類型的比較。
相反,在C#編程中測試字符串相等性的首選方法是使用以下Equals方法:
public bool Equals(string value);public bool Equals(string value, StringComparison comparisonType);
第一個方法簽名(即不帶comparisonType參數(shù))實際上與使用==運算符相同,但是具有顯式應(yīng)用于字符串的好處。它執(zhí)行字符串的序數(shù)比較,基本上是逐字節(jié)比較。在很多情況下,這正是您想要的比較類型,尤其是在比較以編程方式設(shè)置值的字符串(例如文件名,環(huán)境變量,屬性等)時。在這些情況下,只要序數(shù)比較確實是正確的類型這種情況下的比較,使用Equals方法不帶 comparisonType參數(shù)的唯一缺點是,閱讀代碼的人可能不知道您要進(jìn)行哪種類型的比較。
但是,使用Equals每次比較字符串包含comparisonType的方法簽名,不僅可以使代碼更清晰,還可以使您明確考慮需要進(jìn)行哪種類型的比較。這是一件值得做的事情,因為即使英語在序數(shù)比較和對文化敏感的比較之間不能提供很多差異,其他語言也可以提供很多好處,而忽略其他語言的可能性正在為您提供巨大的潛力錯誤的道路。例如:
string s = "strasse";// outputs False:Console.WriteLine(s == "stra?e");Console.WriteLine(s.Equals("stra?e"));Console.WriteLine(s.Equals("stra?e", StringComparison.Ordinal));Console.WriteLine(s.Equals("Stra?e", StringComparison.CurrentCulture));Console.WriteLine(s.Equals("stra?e", StringComparison.OrdinalIgnoreCase));// outputs True:Console.WriteLine(s.Equals("stra?e", StringComparison.CurrentCulture));Console.WriteLine(s.Equals("Stra?e", StringComparison.CurrentCultureIgnoreCase));
最安全的做法是始終為該Equals方法提供comparisonType參數(shù)。以下是一些基本準(zhǔn)則:
?在比較用戶輸入的字符串或要顯示給用戶的字符串時,請使用區(qū)分區(qū)域性的比較(CurrentCulture或CurrentCultureIgnoreCase)。?比較程序字符串時,請使用序數(shù)比較(Ordinal或OrdinalIgnoreCase)。?InvariantCulture和InvariantCultureIgnoreCase一般不被除了在非常有限的情況下使用,因為順序比較是更有效的。如果需要進(jìn)行文化意識比較,則通常應(yīng)針對當(dāng)前文化或其他特定文化進(jìn)行比較。
除了Equals方法之外,字符串還提供了 Compare方法,該方法為您提供有關(guān)字符串相對順序的信息,而不僅僅是進(jìn)行相等性測試。此方法是優(yōu)選的<,<=,>和>=運算符,對于上述的為討論避免C#的問題同樣的原因。
常見的C#編程錯誤#4:使用迭代(而不是聲明性)語句來操作集合
在C#3.0中,向語言[1]添加語言集成查詢[2](LINQ)永遠(yuǎn)改變了查詢和操作集合的方式。從那時起,如果您使用迭代語句來操作集合,那么您本來應(yīng)該使用LINQ。
一些C#程序員甚至不知道LINQ的存在,但是幸運的是,這個數(shù)目正在變得越來越小。但是,許多人仍然認(rèn)為,由于LINQ關(guān)鍵字和SQL語句之間的相似性,它的唯一用途是在查詢數(shù)據(jù)庫的代碼中。
盡管數(shù)據(jù)庫查詢是LINQ語句的一種非常普遍的用法,但它們實際上是在任何可枚舉的集合(即,實現(xiàn)IEnumerable接口的任何對象)上工作的。因此,例如,如果您有一個Accounts數(shù)組,而不是為每個each編寫一個C#List:
decimal total = 0;foreach (Account account in myAccounts) {if (account.Status == "active") {total += account.Balance;}}你可以這樣寫:decimal total = (from account in myAccountswhere account.Status == "active"select account.Balance).Sum();
盡管這是一個非常簡單的示例,說明如何避免這種常見的C#編程問題,但在某些情況下,單個LINQ語句可以輕松替換代碼中的迭代循環(huán)(或嵌套循環(huán))中的數(shù)十個語句。更少的通用代碼意味著更少的引入錯誤的機會。但是請記住,在性能方面可能會有所取舍。在對性能有嚴(yán)格要求的情況下,尤其是在迭代代碼能夠?qū)INQ無法進(jìn)行的集合進(jìn)行假設(shè)的情況下,請確保在這兩種方法之間進(jìn)行性能比較。
常見的C#編程錯誤#5:無法考慮LINQ語句中的基礎(chǔ)對象
LINQ非常適合抽象處理集合的任務(wù),無論它們是內(nèi)存中對象,數(shù)據(jù)庫表還是XML文檔。在理想環(huán)境中,您不需要知道底層對象是什么。但是這里的錯誤是假設(shè)我們生活在一個完美的世界中。實際上,如果相同的LINQ語句恰好采用不同的格式,則當(dāng)它們對完全相同的數(shù)據(jù)執(zhí)行時,它們可以返回不同的結(jié)果。
例如,考慮以下語句:
decimal total = (from account in myAccountswhere account.Status == "active"select account.Balance).Sum();
如果對象的其中一個account.Status等于“活動”(請注意大寫字母A)會怎樣?好吧,如果myAccounts是一個DbSet對象(使用默認(rèn)的不區(qū)分大小寫的默認(rèn)配置設(shè)置),則where表達(dá)式仍會匹配該元素。但是,如果myAccounts位于內(nèi)存陣列中,則它將不匹配,因此將產(chǎn)生總計不同的結(jié)果。等一下 在前面討論字符串比較時,我們看到==運算符對字符串進(jìn)行了序數(shù)比較。那么,為什么在這種情況下==操作員執(zhí)行不區(qū)分大小寫的比較?
答案是,當(dāng)LINQ語句中的基礎(chǔ)對象是對SQL表數(shù)據(jù)的引用時(如本示例中的Entity Framework DbSet對象一樣),該語句將轉(zhuǎn)換為T-SQL語句。然后,操作員將遵循T-SQL編程規(guī)則,而不是C#編程規(guī)則,因此,上述情況下的比較最終不區(qū)分大小寫。
通常,即使LINQ是查詢對象集合的有用且一致的方式,實際上,您仍然需要知道您的語句是否將轉(zhuǎn)換為C#以外的其他內(nèi)容,以確保代碼的行為能夠在運行時達(dá)到預(yù)期。
常見的C#編程錯誤#6:擴展方法使您感到困惑或冒充
如前所述,LINQ語句可在實現(xiàn)IEnumerable的任何對象上工作。例如,以下簡單功能將在任何帳戶集合上累加余額:
public decimal SumAccounts(IEnumerable<Account> myAccounts) {return myAccounts.Sum(a => a.Balance);}
在上面的代碼中,myAccounts參數(shù)的類型聲明為 IEnumerable
public interface IEnumerable<out T> : IEnumerable {IEnumerator<T> GetEnumerator();}
那么該Sum()方法在哪里定義?C#是強類型的,因此,如果對該Sum方法的引用無效,則C#編譯器肯定會將其標(biāo)記為錯誤。因此,我們知道它必須存在,但是在哪里?此外,LINQ為查詢或匯總這些集合提供的所有其他方法的定義在哪里?答案是這Sum()不是IEnumerable接口上定義的方法 。相反,它是在System.Linq.Enumerable類上定義的靜態(tài)方法(稱為“擴展方法”):
namespace System.Linq {public static class Enumerable {...// the reference here to “this IEnumerable<TSource> source” is// the magic sauce that provides access to the extension method Sumpublic static decimal Sum<TSource>(this IEnumerable<TSource> source,Func<TSource, decimal> selector);...}}
那么,什么使擴展方法與任何其他靜態(tài)方法不同,又使我們能夠在其他類中訪問它呢?擴展方法的顯著特征是this其第一個參數(shù)上的 修飾符。這是“魔術(shù)”,可以將其標(biāo)識為編譯器的擴展方法。它修改的參數(shù)的類型(在本例中為IEnumerable
(另一方面,IEnumerable接口名稱和Enumerable定義擴展方法的類的名稱 之間的相似性并沒有什么神奇的。這種相似性只是一個任意的樣式選擇。)
有了這種理解,我們還可以看到sumAccounts上面介紹的功能可以改為如下實現(xiàn):
public decimal SumAccounts(IEnumerable<Account> myAccounts) {return Enumerable.Sum(myAccounts, a => a.Balance);}
我們本可以以這種方式實現(xiàn)它的事實反而引起了一個問題,為什么根本沒有擴展方法? 擴展方法[3]本質(zhì)上是C#編程語言的一種便利,它使您可以將方法“添加”到現(xiàn)有類型,而無需創(chuàng)建新的派生類型,重新編譯或修改原始類型。
通過using [namespace];在文件頂部包含一條語句,可將擴展方法納入范圍。您需要知道哪個C#名稱空間包含要查找的擴展方法,但是一旦知道要查找的內(nèi)容,就很容易確定。
當(dāng)C#編譯器在對象的實例上遇到方法調(diào)用,但未找到在引用的對象類上定義的方法時,它將查看范圍內(nèi)的所有擴展方法,以嘗試查找與所需方法匹配的擴展方法。如果找到一個,它將實例引用作為該擴展方法的第一個參數(shù)傳遞,然后其余參數(shù)(如果有)將作為后續(xù)參數(shù)傳遞給擴展方法。(如果C#編譯器在范圍內(nèi)找不到任何相應(yīng)的擴展方法,它將拋出錯誤。)
擴展方法是C#編譯器中“語法糖”的一個示例,它使我們能夠編寫(通常)更清晰,更可維護(hù)的代碼。更清楚的是,如果您知道它們的用法。否則,可能會有些混亂,尤其是在開始時。
盡管使用擴展方法當(dāng)然具有優(yōu)勢,但它們可能會引起問題,并且對于那些不了解它們或不正確理解它們的開發(fā)人員,C#編程幫助會大聲疾呼。當(dāng)在線查看代碼示例或任何其他預(yù)先編寫的代碼時,尤其如此。當(dāng)此類代碼產(chǎn)生編譯器錯誤時(因為它調(diào)用的類顯然沒有定義方法),人們傾向于認(rèn)為該代碼適用于該庫的不同版本,或完全適用于不同的庫,可能會由于“缺少庫”花費大量時間搜索不存在的新版本。
當(dāng)對象上存在具有相同名稱的方法時,即使熟悉擴展方法的開發(fā)人員仍然偶爾會被捕獲,但是其方法簽名與擴展方法的方法簽名之間存在細(xì)微的差異。尋找錯別字或錯誤可能會浪費很多時間。
在C#庫中使用擴展方法變得越來越普遍。除LINQ之外,Unity Application Block[4]和Web API框架[5]是Microsoft經(jīng)常使用的兩個現(xiàn)代庫的示例,它們也使用擴展方法,并且還有許多其他方法。框架越現(xiàn)代,就越有可能包含擴展方法。
當(dāng)然,您也可以編寫自己的擴展方法。請意識到,盡管擴展方法看起來像常規(guī)實例方法一樣被調(diào)用,但這實際上只是一種幻想。特別是,您的擴展方法不能引用它們正在擴展的類的私有成員或受保護(hù)成員,因此不能完全替代更傳統(tǒng)的類繼承。
常見的C#編程錯誤#7:為當(dāng)前任務(wù)使用錯誤的集合類型
C#提供了大量的各種對象集合,具有以下僅為部分清單:
Array,ArrayList,BitArray,BitVector32,Dictionary<K,V>,HashTable,HybridDictionary,List
盡管在某些情況下,太多的選擇和不足的選擇一樣糟糕,但對于集合對象卻并非如此。可用的選項數(shù)量肯定可以使您受益。預(yù)先花一些時間進(jìn)行研究,然后為您的目的選擇最佳的收集類型。這可能會導(dǎo)致更好的性能和更少的錯誤空間。
如果有一種收集類型專門針對您擁有的元素類型(例如字符串或位),則傾向于首先使用該元素。當(dāng)針對特定類型的元素時,實現(xiàn)通常會更高效。
為了利用C#的類型安全性,通常應(yīng)首選使用通用接口而不是非通用接口。泛型接口的元素是您在聲明對象時指定的類型,而非泛型接口的元素則是object類型。使用非泛型接口時,C#編譯器無法對您的代碼進(jìn)行類型檢查。同樣,在處理原始值類型的集合時,使用非泛型集合將導(dǎo)致這些類型的重復(fù) 裝箱/拆箱[6],與適當(dāng)類型的泛型集合相比,可能會對性能產(chǎn)生重大的負(fù)面影響。
另一個常見的C#問題是編寫您自己的集合對象。但這并不是說它永遠(yuǎn)不合適,但是通過提供.NET提供的廣泛選擇,您可以通過使用或擴展已經(jīng)存在的擴展而不是重新發(fā)明輪子來節(jié)省大量時間。特別是,用于C#和CLI的C5通用集合庫“開箱即用”提供了各種各樣的附加集合,例如持久樹數(shù)據(jù)結(jié)構(gòu),基于堆的優(yōu)先級隊列,哈希索引數(shù)組列表,鏈接列表等等。
常見的C#編程錯誤#8:忽略釋放資源
CLR環(huán)境使用垃圾回收器,因此您無需顯式釋放為任何對象創(chuàng)建的內(nèi)存。實際上,您不能。沒有C ++ delete運算符或free()這樣的函數(shù)。但這并不意味著您在使用完所有對象后就可以忘記所有對象。許多類型的對象封裝了其他類型的系統(tǒng)資源(例如,磁盤文件,數(shù)據(jù)庫連接,網(wǎng)絡(luò)套接字等)。保持這些資源開放狀態(tài)會迅速耗盡系統(tǒng)資源的總數(shù),從而降低性能并最終導(dǎo)致程序錯誤。
盡管可以在任何C#類上定義析構(gòu)函數(shù)方法,但析構(gòu)函數(shù)(在C#中也稱為終結(jié)器)存在的問題是,您不確定是否會調(diào)用它們。它們在將來的不確定時間內(nèi)被垃圾收集器調(diào)用(在單獨的線程上,這可能會導(dǎo)致其他問題)。嘗試通過強制使用垃圾回收來克服這些限制 GC.Collect()不是C#最佳實踐[7],因為這將在線程收集所有符合收集條件的對象時在未知時間內(nèi)阻塞線程。
這并不是說終結(jié)器沒有很好的用途,但是以確定性方式釋放資源并不是其中之一。相反,當(dāng)您在文件,網(wǎng)絡(luò)或數(shù)據(jù)庫連接上進(jìn)行操作時,您希望在完成使用后立即顯式釋放基礎(chǔ)資源。
在幾乎所有環(huán)境中,[8]資源泄漏都是一個問題。但是,C#提供了一種健壯且易于使用的機制,如果使用該機制,則使泄漏的情況更加罕見。.NET框架定義了IDisposable僅由Dispose()方法組成的接口 。任何實現(xiàn)的對象都IDisposable希望在對象的使用者完成對它的操作后才調(diào)用該方法。這導(dǎo)致顯式,確定性的資源釋放。
如果要在單個代碼塊的上下文中創(chuàng)建和處理對象,則忘記調(diào)用基本上是不可原諒的 Dispose(),因為C#提供了一條using語句, Dispose()無論代碼塊如何退出(無論它是例外,return陳述式,或是干脆關(guān)閉區(qū)塊)。是的,這與using前面提到的語句相同,該語句用于在文件頂部包含C#名稱空間。它有第二個完全不相關(guān)的目的,許多C#開發(fā)人員都不知道。即,確保Dispose()在退出代碼塊時對對象進(jìn)行調(diào)用:
using (FileStream myFile = File.OpenRead("foo.txt")) {myFile.Read(buffer, 0, 100);}
通過using 在上面的示例中創(chuàng)建一個塊,您可以確定 myFile.Dispose()在處理完文件后立即調(diào)用該塊,無論是否Read()引發(fā)異常。
常見的C#編程錯誤#9:回避異常
C#將其類型安全性強制實施到運行時。這使您能夠比在C ++等語言中更快地查明C#中的許多類型的錯誤,在C#中錯誤的類型轉(zhuǎn)換可能導(dǎo)致將任意值分配給對象的字段。但是,程序員再次可以浪費這一強大功能,從而導(dǎo)致C#問題。之所以陷入這種陷阱,是因為C#提供了兩種不同的處理方式,一種可以引發(fā)異常,而另一種則不能。有些人會回避異常路由,認(rèn)為不必編寫try / catch塊可以節(jié)省一些代碼。
例如,以下兩種方法可以在C#中執(zhí)行顯式類型轉(zhuǎn)換:
// METHOD 1:// Throws an exception if account can't be cast to SavingsAccountSavingsAccount savingsAccount = (SavingsAccount)account;// METHOD 2:// Does NOT throw an exception if account can't be cast to// SavingsAccount; will just set savingsAccount to null insteadSavingsAccount savingsAccount = account as SavingsAccount;
使用方法2可能發(fā)生的最明顯的錯誤是無法檢查返回值。這可能會導(dǎo)致最終的NullReferenceException,該異常可能會在更晚的時間浮出水面,從而更加難以找到問題的根源。相反,方法1會立即拋出一個 InvalidCastException問題,使問題的根源更加明顯。而且,即使您記得在方法2中檢查過返回值,如果發(fā)現(xiàn)它為空,您將怎么辦?您編寫的方法是否適合報告錯誤?如果強制轉(zhuǎn)換失敗,您還可以嘗試其他方法嗎?如果不是,那么拋出異常是正確的事,因此您最好讓它盡可能地靠近問題的根源。
這是其他兩個常見方法對的兩個示例,其中一個拋出異常而另一個不拋出異常:
int.Parse(); // throws exception if argument can’t be parsedint.TryParse(); // returns a bool to denote whether parse succeededIEnumerable.First(); // throws exception if sequence is emptyIEnumerable.FirstOrDefault(); // returns null/default value if sequence is empty
一些C#開發(fā)人員是如此“異常不利”,以至于他們自動認(rèn)為不拋出異常的方法是更好的。盡管在某些特定情況下這可能是正確的,但作為概括,它根本不正確。作為一個特定的示例,如果您有替代的合法(例如,默認(rèn))操作要發(fā)生,那么將產(chǎn)生異常,那么非異常方法可能是一個合法的選擇。在這種情況下,寫這樣的東西確實更好:
if (int.TryParse(myString, out myInt)) {// use myInt} else {// use default value}
代替:
try {myInt = int.Parse(myString);// use myInt} catch (FormatException) {// use default value}
但是,認(rèn)為TryParse必然是“更好”的方法是不正確的。有時候是這種情況,有時候不是。這就是為什么有兩種方法可以做到這一點。在您所處的環(huán)境中使用正確的方法,請記住,作為開發(fā)人員,異常肯定可以成為您的朋友。
常見的C#編程錯誤#10:允許編譯器警告累積
盡管此問題絕對不是C#特有的,但由于放棄了C#編譯器提供的嚴(yán)格類型檢查的優(yōu)點,因此在C#編程中尤為突出。
產(chǎn)生警告是有原因的。盡管所有C#編譯器錯誤都表明您的代碼有缺陷,但許多警告也是如此。兩者的區(qū)別在于,在出現(xiàn)警告的情況下,編譯器在發(fā)出代碼所表示的指令時沒有問題。即使這樣,它也會發(fā)現(xiàn)您的代碼有些混亂,并且您的代碼有可能無法準(zhǔn)確反映您的意圖。
就本C#編程教程而言,一個常見的簡單示例是,當(dāng)您修改算法以消除對正在使用的變量的使用時,卻忘記了刪除變量聲明。該程序?qū)⑼昝肋\行,但編譯器將標(biāo)記無用的變量聲明。程序運行完美的事實導(dǎo)致程序員忽略了修復(fù)警告原因的方法。此外,編碼人員還利用了Visual Studio功能,該功能使他們可以輕松地將警告隱藏在“錯誤列表”窗口中,從而使他們只能專注于錯誤。很快就出現(xiàn)了數(shù)十種警告,所有這些警告都被幸福地忽略了(或更糟的是隱藏了)。
但是,如果您遲早忽略這種類型的警告,則類似這樣的內(nèi)容很可能會在您的代碼中找到:
class Account {int myId;int Id; // compiler warned you about this, but you didn’t listen!// ConstructorAccount(int id) {this.myId = Id; // OOPS!}}
而且,以Intellisense允許我們編寫代碼的速度,此錯誤并不像看起來那樣不可能。現(xiàn)在,您的程序中出現(xiàn)了嚴(yán)重錯誤(盡管出于已經(jīng)說明的原因,編譯器僅將其標(biāo)記為警告),并且根據(jù)程序的復(fù)雜程度,您可能會浪費大量時間來跟蹤該程序。如果您首先注意了此警告,則只需五秒鐘即可解決此問題。
記住,如果您正在偵聽,C Sharp編譯器會為您提供有關(guān)代碼健壯性的許多有用信息。不要忽略警告。通常,它們只需要花費幾秒鐘的時間進(jìn)行修復(fù),而在發(fā)生新問題時修復(fù)它們可以節(jié)省您的時間。訓(xùn)練自己,使Visual Studio“錯誤列表”窗口顯示“ 0錯誤,0警告”,以便所有警告使您感到不舒服,無法立即解決它們。
當(dāng)然,每個規(guī)則都有例外。因此,有時您的代碼對編譯器來說似乎有些混亂,即使這正是您的預(yù)期。在極少數(shù)情況下,請#pragma warning disable [warning id]僅在周圍使用觸發(fā)警告的代碼,并僅使用其觸發(fā)的警告ID。這將取消該警告,并且僅禁止該警告,因此您仍然可以保持警惕以防出現(xiàn)新的警告。
結(jié)論
C#是一種功能強大且靈活的語言,具有許多可以極大地提高生產(chǎn)率的機制和范例。但是,就像使用任何軟件工具或語言一樣,對其功能的有限了解有時可能更多的是障礙而不是收益,可能會導(dǎo)致生產(chǎn)環(huán)境代碼的問題頻發(fā)。為此,我們需要更多的了解C#語言中那些常見的錯誤,并不斷的持續(xù)優(yōu)化,確保每一行代碼都處于可控的狀態(tài)。
在你的日常開發(fā)過程中,你是否也曾經(jīng)遇到過這些常見錯誤?趕緊跟你身邊的伙伴一起分享吧~
References
[1] 語言: http://msdn.microsoft.com/en-us/library/bb308959.aspx[2] 語言集成查詢: http://msdn.microsoft.com/en-us/library/bb308959.aspx[3] 擴展方法: http://msdn.microsoft.com/en-us/library/bb383977.aspx[4] Unity Application Block: http://msdn.microsoft.com/en-us/library/ff648512.aspx[5] Web API框架: http://msdn.microsoft.com/en-us/library/hh833994%28v=vs.108%29.aspx[6] 裝箱/拆箱: http://msdn.microsoft.com/en-us/library/yz2be5wk.aspx[7] C#最佳實踐: https://orcharddojo.net/orchard-resources/Library/DevelopmentGuidelines/BestPractices/CSharp[8] 所有環(huán)境中,: https://www.toptal.com/c-sharp/how-to-make-an-android-and-ios-app-in-c-on-a-mac


又來一個神奇的網(wǎng)站!

人人影視字幕組涼了,這款美劇APP不能錯過!
