我們知道, C#的語法與C++非常相似,實現從C++向C#的轉變,其困難不在于語言本身,而在于熟悉.NET的可管理環境和對.NET框架的理解。 盡管C#與C++在語法上的變化是很小的,幾乎不會對我們有什么影響,但有些變化卻足以使一些粗心的C++編程人員時刻銘記在心。在本篇文章中我們將討論C++編程人員最容易犯的十個錯誤。 陷阱1: 沒有明確的結束方法 如果使用非可管理性資源,在不使用這些資源后,必須明確地釋放它。對資源的隱性控制是由Finalize方法(也被稱為finalizer)提供的,當對象被銷毀時,它就會被碎片收集程序調用收回對象所占用的資源。 finalizer 應該只釋放被銷毀對象占用的非可管理性資源,而不應牽涉到其他對象。如果在程序中只使用了可管理性資源,那就無需也不應當執行Finalize方法,只有在非可管理性資源的處理中才會用到Finalize方法。由于finalizer需要占用一定的資源,因此應當只在需要它的方法中執行 finalizer。 直接調用一個對象的Finalize方法是絕對不允許的(除非是在子類的Finalize中調用基礎類的Finalize。),碎片收集程序會自動地調用Finalize。 從語法上看,C#中的destructor與C++非常相似,但其實它們是完全不同的。C#中的destructor只是定義Finalize方法的捷徑。因此,下面的二段代碼是有區別的: ~MyClass() MyClass.Finalize()
錯誤2:Finalize和Dispose使用誰? 如果已經使用了Dispose方法,則應當阻止碎片收集程序再對相應的對象執行Finalize方法。為此,需要調用靜態方法 GC.SuppressFinalize,并將相應對象的指針傳遞給它作為參數,Finalize方法就能調用Dispose方法了。據此,我們能夠得到如下的代碼: public void Dispose() // 通知GC不要再調用Finalize方法 public override void Finalize() 對于有些對象,可能調用Close方法就更合適(例如,對于文件對象調用Close就比Dispose更合適),可以通過創建一個private屬性的 Dispose方法和public屬性的Close方法,并讓Close調用Dispose來實現對某些對象調用Close方法。 由于不能確定一定會調用Dispose,而且finalizer的執行也是不確定的(我們無法控制GC會在何時運行),C#提供了一個Using語句來保證 Dispose方法會在盡可能早的時間被調用。一般的方法是定義使用哪個對象,然后用括號為這些對象指定一個活動的范圍,當遇到最內層的括號時, Dispose方法就會被自動調用,對該對象進行處理。 using System.Drawing; } // 編譯器將調用Dispose處理theFont對象 Font anotherFont = new Font("Courier",12.0f); using (anotherFont) } // 編譯器將調用Dispose處理anotherFont對象 } }
在本例的第一部分中,Font對象是在Using語句中創建的。當Using語句結束時,系統就會調用Dispose,對Font對象進行處理。在本例的第二部分,Font對象是在Using語句外部創建的,在決定使用它時,再將它放在Using語句內,當Using語句結束時,系統就會調用 Dispose。 Using語句還能防止其他意外的發生,保證系統一定會調用Dispose。 錯誤3:C#中的值型變量和引用型變量是有區別的 此外,C#語言還把變量分為值類型和引用類型。除非是被包含在一個引用類型中,值類型變量的值保留在棧中,這一點與C++中的變量非常相似。引用類型的變量也是棧的一種,它的值是堆中對象的地址,與C++中的指針非常地相似。值類型變量的值被直接傳遞給方法,引用型變量在被作為參數傳遞給方法時,傳遞的是索引。 類和界面可以創建引用類變量,但需要指出的是,結構數據類型是C#的一種內置數據類型,同時也是一種值型的數據類型。 錯誤4:注意隱性的數據類型轉換 Boxing是隱性的,如果在需要索引型數據類型的地方使用了值型數據類型的變量,值型變量就會隱性地轉化為索引型數據類型的變量。Boxing會影響代碼執行的性能,因此應當盡量避免,尤其是在數據量較大的時候。 如果要將一個打包的對象轉換回原來的值型變量,必須顯性地對它進行解包。解包需要二個步驟:首先對對象實例進行檢查,確保它們是由值型的變量被包裝成的;第二步將實例中的值拷貝到值型變量中。為了確保解包成功,被解包的對象必須是通過打包一個值型變量的值生成的對象的索引。 using System; //打包 // 解包(必須是顯性的)
錯誤5:結構與對象是有區別的 在C#中,結構只是一個用戶自定義的數據類型,并不能取代類。盡管結構也支持屬性、方法、域和操作符,但不支持繼承和destructor。 更重要的是,類是一種索引型數據類型,結構是值型數據類型。因此,結構在表達無需索引操作的對象方面更有用。結構在數組操作方面的效率更高,而在集合的操作方面則效率較低。集合需要索引,結構必須打包才適合在集合的操作中使用,類在較大規模的集合操作中的效率更高。 錯誤6:虛方法必須被明確地覆蓋 如果B公司的一位編程人員要在ListBox上添加一個Sort方法: public class ListBox : Window 在A公司發布新版的Window類之前,這不會有任何問題。如果A公司的編程人員也在Window類中添加了一個Sort方法。 public class Window 在C ++中,Windows類中的Sort方法將成為ListBox類中Sort方法的基礎方法,在希望調用Windows類中的Sort方法時, ListBox類中的Sort方法就會被調用。在C#中,虛擬函數總是被認為是虛擬調度的根。也就是說,一旦C#發現一個虛擬的方法,就不會再在虛擬鏈中查找其他虛擬方法。如果ListBox再次被編譯,編譯器就會生成一個警告信息: "\class1.cs(54,24): warning CS0114: 'ListBox.Sort()' hides 要使當前的成員覆蓋原來的方法,就需要添加override關健字,或者添加new關健字。 要消除警告信息,編程人員必須搞清楚他想干什么。可以在ListBox類中的Sort方法前添加new,表明它不應該覆蓋Window中的虛方法: public class ListBox : Window 這樣就可以清除警告信息。如果編程人員確實希望覆蓋掉Window中的方法,就必須使用override關健字來顯性地表明其意圖。
錯誤7:類成員變量的初始化 Employee::Employee(int theAge, int theSalaryLevel): 這種方法在C#中是非法的。盡管仍然可以初始化基礎類,但象上面的代碼那樣對成員變量初始化就會引起編譯錯誤。在C#中,我們可以在定義成員變量時的同時對它進行初始化: Class Employee : public Person 注意:必須明確地定義每個變量的訪問權限。 錯誤8:布爾型變量與整型變量是兩回事兒 在C#中,布爾型變量與整型變量并不相同,因此下面的代碼是不正確的: if( someFuncWhichReturnsAValue() ) if someFuncWhichReturnsAValue返回零表示false,否則表示true的想法已經行不通了。這樣的好處是原來存在的將賦值運算與相等相混淆的錯誤就不會再犯了。因此下面的代碼: if ( x = 5 ) 在編譯時就會出錯,因為x=5只是把5賦給了X,而不是一個布爾值。 錯誤9:switch語句中會有些語句執行不到 switch (i)
switch (i) 如果case語句不執行任何代碼,則所有的語句都會被執行。如下面的代碼: switch (i) 錯誤10:C#中的變量要求明確地賦值 如果只是通過索引向方法傳遞一個變量,并且該變量是方法的輸出變量,這是就會帶來問題。例如,假設有一個方法,它返回當前時間的小時、分、秒,如果象下面這樣編寫代碼: int theHour; 如果在使用theHour、theMinute和theSecond這三個變量之前沒有對它們進行初始化,就會產生一個編譯錯誤: Use of unassigned local variable 'theHour' 我們可以通過將這些變量初始化為0或其他對方法的返回值沒有影響的值,以解決編譯器的這個小問題: int theHour = 0; 這樣就有些太麻煩了,這些變量傳遞給GetTime方法,然后被改變而已。為了解決這一問題,C#專門針對這一情況提供了out參數修飾符,它可以使一個參數無需初始化就可以被引用。例如,GetTime中的參數對它本身沒有一點意義,它們只是為了表達該方法的輸出。在方法中返回之前,Out參數中必須被指定一個值。下面是經過修改后的GetTime方法: public void GetTime(out int h, out int m, out int s) 下面是新的GetTime方法的調用方法: timeObject.GetTime( out theHour, out theMinute, out theSecond);
|
溫馨提示:喜歡本站的話,請收藏一下本站!