C# 2.0对现有语法的改进
C# 2.0對現有語法的改進
原注:lover_P
出處:
[自序]
??? 盡管Microsoft Visual Studio .NET 2005(過去好像叫Visual Studio .NET 2004)一再推遲其發布日期,但廣大開發者對其的猜測以及各種媒體對其各方面的“曝光”也似乎已經充斥了網絡。但與C#有關的文章似乎無外乎兩個方面:VS.NET 2005 IDE特性、介紹C# 2.0中引入的“四大特性(泛型、匿名方法、迭代器和不完整類型)”。對IDE的研究我不想談了,微軟就是微軟,他的窗口應用程序總是沒得說的。而就語言本身的改進來說,在我聽完了Anders Hejlsberg在Microsoft Professional Developers Conference 2003(2003.10, Los Angeles, CA)上的演講后,發現除了這四大特性之外,還有一些鮮為人知的特性,甚至在微軟官方的文檔《C# Language Specification Version 2.0》中都沒有提到。而這些特性卻更加提高了語言的邏輯性。于是我編寫了大量實際程序研究了這些特性,終于著成本文。本打算發表在《CSDN開發高手》雜志上的,可無奈水平有限,只能留在這個角落里貽笑大方了。希望能夠對那些對C#語言有著濃厚興趣的朋友有些幫助。
——lover_P 于北京工業大學1號樓221寢室
[正文]
??? 微軟在其即將推出的C#2.0(Visual C# Whidbey)中,添加了許多令程序員感到振奮的新特性。除了泛型(Generic)、迭代器(Iterator)、匿名方法(Anonmynous)和分部類型(Partial Type)等重大的改進,對現有的語法進行了很大的改進,極大地方便了.NET框架程序設計的工作,并且進一步加強了C#語言獨有的高邏輯性。在本文中,我將向大家介紹一下這些改進。(文中C#指代的是C#1.2及以前的版本,而C#2.0指代的是微軟尚未正式推出的C# Whidbey;文章中的所有代碼均在版本號為8.00.30703.4的C#編譯器下進行了測試,標有*的錯誤消息得自版本號為7.10.3052.4的C#編譯器。)
靜態類
??? 使用C#進行.NET框架程序設計的人應該都知道,無法將一個類聲明為靜態的。例如,下面的類聲明:
| public static class A { ??? static int i; } |
在C#中是無效的,當我們嘗試編譯這段代碼時會得到下面的編譯錯誤*:
| error CS0106: 修飾符“static”對該項無效 |
??? 由于無法用static修飾符修飾一個類,我們在類中總是能夠既聲明靜態成員又聲明實例成員。這無疑會帶來很大的靈活性。但是,如果我們希望一個類是靜態的,也就是希望強制要求這個類中的所有成員都應該為靜態的,就無能為力了,唯一能做的就是自己注意將所有的成員聲明為static。當我們忘記對一個本應是靜態的成員使用static修飾符(盡管這是一個“低級錯誤”,但仍有可能發生)時,將會產生難以預料的錯誤。最重要的是,對于一個邏輯上的靜態類(所有成員均使用static修飾符進行聲明的類),我們甚至可以聲明該類的一個變量并使用new操作符產生該類的實例!這顯然不是我們所期望的。
??? 而在C#2.0中,則提供了靜態類這一概念,允許static修飾符對類進行修飾,上面的代碼得以通過編譯。如果一個類聲明中包含了static修飾符,那么這個類中的所有成員將被強制要求聲明為靜態的。這時,如果我們故意在類中聲明實例成員或是不小心忘記了成員聲明中的static修飾符,如下面代碼所示:
| public static class A { ??? int i; } |
則編譯器會報告錯誤:
| error CS0708: 'A.i': cannot declare instance members in a static class |
同時,如果我們聲明該類的變量或是試圖建立該類的一個實例時,如下面的代碼:
| public class Test { ??? A a;? ??????????? // error CS0723 ??? void Foo() { ??????? a = new A();? // error CS0712 ??? } } |
則會得到下面的兩個編譯錯誤:
| error CS0723: Cannot declare variable of static type 'A' error CS0712: Cannot create an instance of the static class 'A' |
??? 很顯然,C#2.0中對靜態類的支持極大程度地避免了我們在書寫程序中的意外失誤,尤其是加強了靜態類的邏輯性,提高了代碼的可讀性。
屬性的可訪問性限定
??? C#為我們提供了相當方便的屬性定義,使得我們可以像訪問類的公有變量成員那樣訪問類的屬性,但還可以同時得到像訪問函數那樣的安全性。然而,C#只允許屬性的設置動作(set{...})和獲取動作(get{...})具有相同的可訪問性(由屬性聲明的public、internal和private修飾符指定)。那么,當我們希望允許任何程序集中的類獲取一個類的屬性,但只允許該類所在的程序集或該類的私有成員才能設置該屬性時,我們只能將這個屬性聲明為公有且只讀(即使用public修飾符聲明但只有get{}域),而內部的或私有的成員只能通過設置與該屬性相關的內部或私有的變量成員的值來完成屬性的設置工作:
| public class A { ??? int _intValue;? // 與屬性相關的一個int類型的成員變量 ??? // 公有且只讀的屬性,允許任何類獲取該屬性的值: ??? public int Value { ??????? get { return _intValue; } ??? } ??? // 下面的方法需要設置上面的屬性, ??? // 但只能通過訪問私有成員變量來完成, ??? // 并且要另外進行錯誤處理 ??? private void SomeMethod() { ??????? int i; ??????? // ...... ??????? // 下面的if-else語句僅用來設置屬性值: ??????? if(0 < i && i < 10) { ??????????? _intValue = i; ??????? } ??????? else { ??????????? // 錯誤處理 ??????? } ??? } } |
??? 很明顯,這種做法是非常麻煩的。如果在多個地方改變了成員變量的值會使代碼變得冗長不可讀,還很有可能會產生錯誤,譬如該類有另外一個方法:
| private void AnotherMethod() { ??? int i; ??? // ...... ??? // 下面的if-else語句僅用于設置屬性值, ??? // 但其對i的區間檢測發生了錯誤 ??? if(0 < i && i <= 10) { ??????? _intValue = i; ??? } ??? // 并且沒有進行錯誤處理 ??? // ...... } |
??? 上面的方法對將要賦給私有變量成員的值的檢查區間是錯誤的,這種錯誤是很有可能發生的。一旦調用了這個方法,_intValue很有可能具有錯誤的值,而訪問了Value屬性的外部程序集將會出現邏輯錯誤。這種錯誤的解決是相當困難的。并且,如果一個小組中的其他成員負責設計同一程序集中其他的類,要求他們在方法中書寫如此大量的代碼并要進行錯誤檢查是不人道的。
??? 當然,我們可能會想到將這種設置屬性值的工作放到一個內部方法中集中進行:
| // 程序集內部的類或該類的私有成員通過 // 下面的內部方法對上面的屬性進行設置工作 internal void _setIntValue(int newValue) { ??? if(0 < newValue && newValue < 10) { ??????? _intValue = newValue; ??? } ??? else { ??????? throw new System.InvalidArgumentException ( ??????????? “The new value must greater than 0 and less than 10” ??????? ); ??? } } // 下面的方法需要對上述屬性進行設置 private void SomeMethod() { ??? int i; ??? // ...... ??? _setIntValue(i); ?// 通過調用內部方法進行 } |
??? 這樣做雖然避免了邏輯錯誤的出現(至少使出現了錯誤時的解決工作變得容易),但其可讀性仍然不理想,尤其是邏輯性很差,與“屬性”本身的意義相去甚遠。
??? 然而C#2.0允許我們對屬性的get{}和set{}域分別設置可訪問性,我們能夠將上面的代碼簡單地寫作:
| public class A { ??? int _intValue; ?// 與屬性相關的一個int類型的成員變量 ??? // 公有的屬性, ??? // 允許任何類獲取該屬性的值, ??? // 但只有程序集內部的類和該類中的私有成員 ??? // 能夠設置屬性的值 ??? public int Value { ??????? get { ??????????? return _intValue; ??????? } ??????? internal set { ??????????? if(0 < value && value < 10) { ??????????????? _intValue = value; ??????????? } ??????????? else { ??????????????? throw new System.InvalidArgumentException ( ??????????????????? “The new value must greater than 0 and less than 10” ??????????????? ); ??????????? } ??????? } ??? } ?// property ??? // 下面的方法需要對上述屬性進行設置 ??? private void SomeMethod() { ??????? int i; ??????? // ...... ??????? Value = i; ??? } } |
??? 尤其在程序集中的其他類的成員中訪問該屬性時相當方便:
| // 這是同一個程序集中另外的一個類: public class B { ??? public A SomeMethod() { ??????? A a = new A(); ??????? a.Value = 8; ?// 這里對屬性進行設置,方便! ??????? return a; ??? } } |
??? 可以看出,能夠對屬性的獲取和設置操作分別設置可訪問性限定極大地增強了C#程序的可讀性和語言邏輯性,寫出的程序也具有更強的可維護性。
命名空間別名
??? 在C#中,使用類(如聲明成員變量或調用靜態方法等)的時候需要指定類的完全名稱,即命名空間前綴加類的名字。如果我們要在控制臺上打印“Hello, world!”,則需要寫:
| System.Console.WriteLine(“Hello, world!”); |
其中,System是命名空間,Console是類的名字,WriteLine()是我們要調用的方法。
??? 這樣的要求顯然會使代碼變得異常冗余。因此,C#為我們提供了using關鍵字(指令)。通過使用using指令,我們可以向編譯器指定一系列命名空間,當程序中出現了類名字時,編譯器會自動到這些命名空間中查找這個類。因此,上面的代碼可以寫作:
| using System; // ...... public class Test { ??? public static void Main() { ??????? Console.WriteLine(“Hello, world!”); ??? } } |
??? 呵呵,這不就是經典的“Hello World”范例么?很顯然,這種寫法方便得多,可以極大地提高開發效率。
??? 然而,兩個命名空間中很可能具有同名的類,而我們恰好需要用到這些同名的類。這種情況會經常發生。譬如在.net框架類庫中就存在有三個Timer類:System.Timer.Timer、System.Threading.Timer和System.Windows.Forms.Timer。我們很可能需要兩個Timer類:一個System.Timer.Timer用于在后臺以固定的時間間隔檢查應用程序或系統狀態,一個System.Windows.Forms.Timer用于在用戶界面中顯示簡單動畫。這時,盡管我們使用了using指令,我們仍然需要在這些類出現時加上命名空間:
| using System.Timer; using System.Windows.Forms; // ...... public class MainForm : Form { ??? System.Timer.Timer CheckTimer; ??? System.Windows.Forms.Timer AnimateTimer; ??? // ...... } |
??? 這樣的程序仍然顯得冗長。
??? 在C#2.0中,using指令的使用得到了擴展,我們可以使用using指令來為命名空間指定一個別名。當我們需要引用這個命名空間時,可以簡單地使用它的別名。不過,使用別名和使用命名空間的使用完全相同。當我們引用一個命名空間中的類型的時候,只需要在命名空間后面加一個圓點“.”再跟上類型名稱即可;而引用一個別名所代表的命名空間中的類型時,寫法是一樣的。那么,上面的例子可以寫作:
| using SysTimer = System.Timer; using WinForm = System.Windows.Forms; // ...... public class MainForm : WinForm.Form { ??? SysTimer.Timer CheckTimer;? // 與命名空間的使用完全相同 ??? WinForm.Timer AnimateTimer; ??? // ...... } |
??? 我們可以看到,這樣的代碼要簡潔得多。
編譯器指令
??? 在我們調試C#程序時,經常會聲明一些臨時變量用來監測程序狀態,并在調試完成后將這些聲明刪除。而當我們聲明了這樣的臨時變量,在調試過程中卻沒有用到的時候,我們通常會得到大量的如:
| warning CS0168: The variable 'exp' is declared but never used |
的警告。然而,我們很清楚這樣的警告是無害的。同樣,很多其他時候我們也會得到一些警告,但我們不得不從大量的無害的警告中尋找我們需要的錯誤消息。
??? C#2.0為我們提供了一條新的編譯器指令:pragma warning,使得我們能夠在一段代碼中禁止一些我們確認無害的警告(通過指定警告的編號)。以前,這種工作只能由特定的編譯器(譬如C#編譯器的/nowarn選項)或相應的IDE選項來完成。例如,我們可以用下列代碼來禁止產生上述的“未使用參數”的警告:
| public class Test { ??? public void SomeMethod() { ??????? // 下面的編譯器指令禁止了“未使用參數”的警告: #pragma warning disable 0168 ??????? int tempStatus; ??????? // ...... ??????? // 下面的編譯器指令重新允許產生“未使用參數”的警告: #pragma warning restore 0168 ??? } } |
??? 這樣,當編譯器編譯SomeMethod()方法時,將不會產生上述的“未使用參數”的警告,但在編譯其它代碼段時,仍然會產生該警告,因為我們用#pragma warning restore指令重新打開了該警告。
固定大小緩沖區
??? 最后,除了上述的一些特性外,C#2.0還提供了“固定大小緩沖區(Fixed Size Buffers)”的新特性。即像C語言那樣可以在結構中聲明一個固定大小的數組,這通過System.Runtime.CompilerServices.FixedBufferAttribute屬性和Fixed關鍵字實現(參見參考文獻第26頁):
| [System.Runtime.CompilerServices.FexedBuffer] public struct Buffer { ??? public fixed char Buffer[128]; } |
??? 但由于我的編譯器尚未支持這一特性,手頭又沒有相應的資料,在此就不做介紹了。
??? 以上是我對C#2.0中除了泛型、迭代器、匿名方法和分部類型等重大改進之外的一些對現有特性進行的改進的簡要介紹。這些改進看起來很微小,卻極大程度地增強了C#語言的邏輯性,使得我們能夠寫出更加漂亮且可維護性更強的代碼。我的介紹是非常簡略的,甚至可能有錯誤,希望大家指教。(聯系方式:lyb_dotNET@hotmail.com)
參考文獻
??? [1]《Visual C# Whidbey: Language Ehancements》,Anders Hejlsberg在Microsoft Professional Developers Conference 2003(2003.10, Los Angeles, CA)上的演講及其PowerPoint文檔。
總結
以上是生活随笔為你收集整理的C# 2.0对现有语法的改进的全部內容,希望文章能夠幫你解決所遇到的問題。