C#圖解教程 第二十五章 其他主題
其他主題
概述
在本章中,我會介紹使用C#時的一些重要而又不適合放到其他章節的主題,包括字串操作、可空型別、Main方法、文件註釋以及巢狀型別。
字串
對於內部計算來說0和1很適合,但是對於人類可讀的輸入和輸出,我們需要字串。BCL提供了很多能讓字串操作變得更簡單的類。
C#預定義的string型別代表了.NET的System.String類。對於字串,最需要理解的概念如下。
- 字串是Unicode字串陣列
- 字串是不可變的(immutable ) 它們不能被修改
string型別有很多有用的字串操作成員,包括允許我們進行諸如檢測長度、改變大小寫、連線字串等一些有用的操作。下表列出了其中一些最有用的成員。
從上表中的大多數方法的名字來看,好像它們都會改變字串物件。其實,它們不會改變字串而是返回了新的副本。對於一個string,任何“改變”都會分配一個新的恆定字串。
例如,下面的程式碼宣告並初始化了一個叫做s的字串。第一個WriteLine語句呼叫了s的ToUpper方法,它返回了字串中所有字母為大寫形式的副本。最後一行輸出了s的值,可以看到,字串並沒有改變。
string s = "Hi there."; Console.WriteLine("{0}",s.ToUpper()); //輸出所有字母為大寫的副本 Console.WriteLine("{0}", s); //字串沒有變
筆者自己編碼時,發現上表中很有用的一個方法是Split。它將一個字串分隔為若干子字串,並將它們以陣列的形式返回。將一組按預定位置分隔字串的分隔符傳給Split方法,就可以指定如何處理輸出陣列中的空元素。當然,原始字串依然不會改變。
下面的程式碼顯示了一個使用Split方法的示例。在這個示例中,分隔符由空字元和4個標點符號組成。
class Program { static void Main() { string s1="hi there!this,is:a string."; char[] delimiters={' ','!',',',':','.'}; string[] words=s1.Split(delimiters,StringSplitOption.RemoveEmptyEntries); Console.WriteLine("Word Count:{0}\n\rThe Words…",words.Length); foreach(string s in words) { Console.WriteLine(" {0}",s); } } }
使用 StringBuilder類
StringBuilder類可以幫助你動態、有效地產生字串,並且避免建立許多副本。
- StringBuilder類是BCL的成員,位於System.Text名稱空間中
- StringBuilder物件是Unicode字元的可變陣列
例如,下面的程式碼宣告並初始化了一個StringBuilder型別的字串,然後輸出了它的值。第四行程式碼通過替換初始字串的一部分改變了其實際物件。當輸出它的值,隱式呼叫ToString時,我們可以看到,和string型別的物件不同,StringBuilder物件確實被修改了。
using System; using System.Text; class Program { static void Main() { StringBuilder sb = new StringBuilder( "Hi there."); Console.WriteLine( "{0}", sb.ToString()); sb.Replace( "Hi", "Hello"); Console.WriteLine( "{0}", sb.ToString()); } }
當依據給定的字串建立了StringBuilder物件之後,類分配了一個比當前字串長度更長的緩衝區。只要緩衝區能容納對字串的改變就不會分配新的記憶體。如果對字串的改變需要的空間比緩衝區中的可用空間多,就會分配更大的緩衝區,並把字串複製到其中。和原來的緩衝區一樣,新的緩衝區也有額外的空間。
要獲取StringBuilder對應的字串內容,我們只需要呼叫它的ToString方法即可。
把字串解析為資料值
字串都是Unicode字元的陣列。例如,字串"25.873"是6個字元而不是一個數字。儘管它看上去像數字,但是我們不能對它使用數學函式。把兩個字串進行“相加”只會串聯它們。
- 解析允許我們接受表示值的字串,並且把它轉換為實際值
- 所有預定義的簡單型別都有一個叫做Parse的靜態方法,它接受一個表示這個型別的字串值,並且把它轉換為型別的實際值
以下語句給出了一個使用Parse方法語法的示例。注意,Parse是靜態的,所以我們需要通過目標型別名來呼叫它。
double dl = double.Parse("25.873"); ↑ ↑ 目標型別 要轉換的字串
以下程式碼給出了一個把兩個字串解析為double型值並把它們相加的示例:
static void Main() { string s1 = "25.873"; string s2 = "36.240"; double dl = double.Parse(s1); double d2 = double.Parse(s2); double total = dl + d2; Console.WriteLine("Total: {0}", total); }
這段程式碼產生了如下輸出:
關於Parse有一個常見的誤解,由於它是在操作字串,會被認為是string類的成員。其實不是,Parse根本不是一個方法,而是由目標型別實現的很多個方法。
Parse方法的缺點是如果不能把string成功轉換為目標型別的話會丟擲一個異常。異常是昂貴的操作,應該儘可能在程式設計中避免異常。TryParse方法可以避免這個問題。有關TryParse需要知道的亟要事項如下。
- 每一個具有Parse方法的內建型別同樣都有一個TryParse方法
- TryParse方法接受兩個引數並且返回一個bool值
- 第一個引數是你希望轉換的字串
- 第二個是指向目標型別變數的引用的out引數
- 如果TryParse成功,返回true,否則返回false
如下程式碼演示了使用int.TryParse方法的例子:
class Program { static void Main() { string parseResultSummary; string stringFirst = "28"; int intFirst; 輸入字串 輸出變置 ↓ ↓ bool success = int.TryParse( stringFirst, out intFirst ); parseResultSummary = success ? "was successfully parsed" :"was not successfully parsed"; Console.WriteLine( "String {0} {1}", stringFirst, parseResultSummary ); string stringSecond = "vt750"; int intSecond; 輸入字串 輸出變最 ↓ ↓ success = int.TryParse( stringSecond, out intSecond ); parseResultSummary = success ? "was successfully parsed" :"was not successfully parsed"; Console.WriteLine( "String {0} {1}", stringSecond, parseResultSummary ); } }
關於可空型別的更多內容
在第3章中我們已經介紹過了可空型別。你應該記得,可空型別允許我們建立一個值型別變數並且可以標記為有效或無效,這樣我們就可以有效地把值型別設定為"null"。我本來想在第3章中介紹可空型別及其他內建型別,但是既然現在你對C#有了更深入的瞭解,現在正是時候介紹其更復雜的方面。
複習一下,可空型別總是基於另外一個叫做基礎型別(underlying type)的已經被宣告的型別。
- 可以從任何值型別建立可空型別,包括預定義的簡單型別
- 不能從引用型別或其他可空型別建立可空型別
- 不能在程式碼中顯式宣告可空型別,只能宣告可空型別的變童。之後我們會看到,編譯器會使用泛型隱式地建立可空型別
要建立可空型別的變數,只需要在變數宣告中的基礎型別的名字後面加一個問號。
例如,以下程式碼聲明瞭一個可空int型別的變數。注意,字尾附加到型別名--而不是變數名稱。
字尾 ↓ int? myInt=28;
有了這樣的宣告語句,編譯器就會產生可空型別並關聯變數型別。可空型別的結構如下圖所示。
- 基礎型別的例項
- 幾個重要的只讀屬性
- HasValue屬性是bool型別,並且指示值是否有效
- Value屬性是和基礎型別相同的型別並且返回變最的值--如果變數有效的話
使用可空型別基本與使用其他型別的變數一樣。讀取可空型別的變數返回其值。但是你必須確保變數不是null的,嘗試讀取一個null的變數會產生異常。
- 跟任何變數一樣,要獲取可空型別變數的值,使用名字即可
- 要檢測可空型別是否具有值,可以將它和null比較或者檢查它的HasValue屬性
int? myInt1=15; if(myInt1!=null) { Console.WriteLine("{0}",myInt1); }
你可以像下面那樣顯式使用兩個只讀屬性。讀取可空型別的變數返回其值。但是你必須確保變數不是null的,嘗試讀取一個null的變數會產生異常。
可空型別和相應的非可空型別之間可輕鬆實現轉換。有關可空型別轉換的重要事項如下:
- 非可空型別和相應的可空版本之間的轉換是隱式的,也就是說,不需要強制轉換
- 可空型別和相應的可空版本之間的轉換是顯式的
例如,下面的程式碼行顯示了兩個方向上的轉換。第一行int型別的字面量隱式轉換為int?型別的值,並用於初始化可空型別的變數。第二行,變數顯式轉換為它的非可空版本。
int? myInt1 = 15; // 將int隱式轉換為 int? int regInt = (int) myInt1; // 將int?顯式轉換為int
為可空型別賦值
可以將以下三種類型的值賦給可空型別的變數:
- 基礎型別的值
- 同一可空型別的值
- Null值
以下程式碼分別給出了三種類型賦值的示例:
int? myI1,myI2,myI3 myI1 = 28; //基礎型別的值 myI2 = myI1; //可空型別的值 myI3 = null; //null Console.WriteLine("myI1: {0}, myI2: {1}", myI1, myI2);
使用空接合運算子
標準算術運算子和比較運算子同樣也能處理可空型別。還有一個特別的運算子叫做空接合運算子(null coalescing operator),它允許我們在可空型別變數為null時返回一個值給表示式。
空接合運算子由兩個連續的問號組成,它有兩個運算元。
- 第一個運算元是可空型別的變數
- 第二個是相同基礎型別的不可空值
- 在執行時,如果第一個運算元運算後為null,那麼第二個運算元就會被返回作為運算結果
int? myI4 = null; 空接合運算子 ↓ Console.WriteLine("myI4: {0}", myI4 ?? -l); myI4 = 10; Console.WriteLine("myI4: {0}", myI4 ?? -1);
如果你比較兩個相同可空型別的值,並且都設定為null,那麼相等比較運算子會認為它們是相等的(==和!=)。
例如,在下面的程式碼中,兩個可空的int被設定為null,相等比較運算子會聲 明它們是相等的。
int? i1 = null,i2 = null; //都為空 if (i1 == i2) //返回true { Console.WriteLine("Equal"); }
使用可空使用者自定義型別
至此,我們已經看到了預定義的簡單型別的可空形式。我們還可以建立使用者自定義值型別的可空形式。這就引出了在使用簡單型別時沒有遇到的其他問題。
主要問題是訪問封裝的基礎型別的成員。一個可空型別不直接暴露基礎型別的任何成員。例如,來看看下面的程式碼和下圖中它的表示形式。程式碼聲明瞭一個叫做MyStruct的結構(值型別),它有兩個公共欄位。
- 由於結構的欄位是公共的,所以它可以被結構的任何例項所訪問到,如圖左部分所示。
- 然而,結構的可空形式只通過Value屬件暴露基礎型別,它不直接暴露它的任何成員。儘管這些成員對結構來說是公共的,但是它們對可空型別來說不是公共的,如圖右部分所示
struct MyStruct { public int X; public int Y; public MyStruct(int xVal,int yVal) { X=xVal; Y=yVal; } } class Program { static void Main() { MyStruct? mSNull=new MyStruct(5,10); … } }
例如,以下程式碼使用之前宣告的結構並建立了結構和它對應的可空型別的變數。在程式碼的第三行和第四行中,我們直接讀取結構變數的值。在第五行和第六行中,就必須從可空型別的Value屬性返回的值中進行讀取。
MyStruct mSStruct=new MyStruct(6,11); MyStruct? mSNull=new MyStruct(5,10); Console.WriteLine("mSStruct.X: {0}",mSStruct.X); Console.WriteLine("mSStruct.Y: {0}",mSStruct.Y); Console.WriteLine("mSNull.X: {0}",mSNull.Value.X); Console.WriteLine("mSNull.Y: {0}",mSNull.Value.Y);
Nullable<T>
可空型別通過一個叫做System.Nullable<T>
的.NET型別來實現,它使用了C#的泛型特性。C#可空型別的問號語法是建立Nullable<T>
型別變數的快捷語法,在這裡T是基礎型別。Nullable<T>
接受了基礎型別並把它嵌入結構中,同時給結構提供可空型別的屬性、方法和建構函式。
我們可以使用Nullable<T>
這種泛型語法,也可以使用C#的快捷語法。快捷語法更容易書寫和理解,並且也減少了出錯的可能性。以下程式碼使用Nullable<T>
語法為之前示例中宣告的 MyStruct 結構建立一個叫做mSNull的Nullable<MyStruct>
型別。
Nullable<MyStruct> mSNull = new Nullable<MyStruct>();
下面的程式碼使用了問號語法,完全等同於Nullable<T>
語法:
MyStruc? mSNull=new MyStruct();
Main 方法
每一個C#程式都必須有一個入口點--一個必須叫做Main的方法。
在貫穿本書的示例程式碼中,都使用了一個不接受引數並且也不返回值的Main方法。然而,一共有4種形式的Main可以作為程式的入口點。這些形式如下:
- static void Main {…}
- static void Main(string[] args) {…}
- static int Main() {…}
- static int Main(string[] args) {…}
前面兩種形式在程式終止後都不返回值給執行環境。後面兩種形式則返回int值。如果使用返回值,通常用於報告程式的成功或失敗,0通常用於表示成功。
第二種和第四種形式允許我們在程式啟動時從命令列向程式傳入實參,也叫做引數。命令列引數的一些重要特性如下。
- 可以有0個或多個命令列引數。即使沒有引數,args引數也不會是null,而是一個沒有元素的陣列
- 引數由空格或製表符隔開
- 每一個引數都被程式解釋為是字串,但是你無須在命令列中為引數加上引號
例如,下面叫做CommandLineArgs的程式接受了命令列引數並列印了每一個提供的引數:
class Program { static void Main(string[] args) { foreach (string s in args) { Console.WriteLine(s); } } }
如下命令列使用5個引數執行CommandLineArgs程式。
CommandLineArgs Jon Peter Beth Julia Tammi ↑ ↑ 可執行程式名 引數
前面的程式和命令列產生了如下的輸出:
其他需要了解的有關Main的重要事項如下。
- Main必須總是宣告為static
- Main可以被宣告為類或結構
一個程式只可以包含Main的4種可用入口點形式中的一種宣告。當然,如果你宣告其他方法的名稱為Main,只要它們不是4種入口點形式中的一種就是合法--但是,這樣做是非常容易混淆的。
Main的可訪問性
Main可以被宣告為public或private。
- 如果Main被宣告為private,其他程式集就不能訪問它,只有執行環境才能啟動程式
- 如果Main被宣告為public,其他程式集就可以呼叫它
然而,無論Main宣告的訪問級或所屬類或結構的訪問級別是什麼,執行環境總是能訪問Main。
預設情況下,當Visual Studio建立了一個專案時,它就建立了一個程式框,其中的Main是隱式private。如果需要,你隨時可以新增public修飾符。
文件註釋
文件註釋特性允許我們以XML元素的形式在程式中包含文件(第19章介紹XML)。Visual Studio會幫助我們插入元素,以及從原始檔中讀取它們並複製到獨立的XML檔案中。
下圖給出了一個使用XML註釋的概要。這包括如下步驟。
- 你可以使用Visual Studio來產生帶有嵌人了XML的原始檔。Visual Studio會自動插入大多數重要的XML元素
- Visual Studio從原始檔中讀取XML並且複製XML程式碼到新的檔案
- 另外一個叫做文件編譯器的程式可以獲取XML檔案並且從它產生各種型別的檔案
之前的Visual Studio版本包含了基本的文件編譯器,但是它在Visual Studio 2005釋出之前被刪除了。微軟公司正在開發一個叫做Sandcastle的新文件編譯器,它已經被用來生成.NET框架的文件。從http://sandcastle.codeplex.com 可更詳細地瞭解以及免費下載這個軟體。
插入文件註釋
文件註釋從3個連續的正斜槓開始。
- 前兩個斜槓指示編譯器這是一行註釋,並且需要從程式的解析中忽略
- 第三個斜槓指示這是一個文件註釋
例如,以下程式碼中前4行就是有關類定義的文件註釋。這裡使用<summary>
XML標籤。在欄位宣告之上有3行來說明這個欄位--還是使用<summary>
標籤。
///<summary> ← 類的開始XML標籤 /// This is class MyClass, which does the following wonderful things, using /// the following algorithm. …Besides those, it does these additional /// amazing things. ///</summary> ← 關閉 XML 標籤 class MyClass { ///<summary> /// Field1 is used to hold the value of … ///</summary> public int Field1 = 10; … }
每一個XML元素都是當我們在語言特性(比如類或類成員)的宣告上輸入3條斜槓時,ViSual Studio 自動增加的。
例如,從下面的程式碼可以看到,在MyClass類宣告之上的2條斜槓:
// class MyClass {…}
只要我們增加了第三條斜槓,Visual Studio會立即擴充套件註釋為下面的程式碼,而我們無須做任何事情。然後我們就可以在標籤之間輸入任何希望註釋的行了。
/// <summary> 自動插入 /// 自動插入 /// </summary> 自動插入 class MyClass {…}
使用其他XML標籤
在之前的示例中,我們看到了summay XML標籤的使用。C#可識別的標籤還有很多。下表列出了最重要的一些。
巢狀型別
我們通常直接在名稱空間中宣告型別。然而,我們還可以在類或結構中宣告型別。
- 在另一個型別宣告中宣告的型別叫做巢狀型別。和所有型別宣告一樣,巢狀型別是型別例項的模板
- 巢狀型別像封閉型別(enclosing type)的成員一樣宣告
- 巢狀型別可以是任意型別
- 巢狀型別可以是類或結構
例如,以下程式碼顯示了MyClass類,其中有一個叫做MyCounter的巢狀類。
class MyClass //封閉類 { class MyCounter//巢狀類 {…} … }
如果一個型別只是作為幫助方法並且只對封閉型別有意義,可能就需要宣告為巢狀型別了。不要跟巢狀這個術語混淆。巢狀指宣告的位置--而不是任何例項的位置。儘管巢狀型別的宣告在封閉型別的宣告之內,但巢狀型別的物件並不一定封閉在封閉型別的物件之內。巢狀型別的物件(如果建立了的話)和它沒有在另一個型別中宣告時所在的位置一樣。
例如,下圖顯示了前面程式碼框架中的MyClass物件和MyCounter物件。另外還顯式了MyClass類中的一個叫做Counter的欄位,這就是指向巢狀型別物件的引用,它在堆的另一處。
巢狀類的示例
以下程式碼把MyClass和MyCounter完善成了完整的程式。MyCounter實現了一個整數計數器,從0開始並且使用++運算子來遞增。當MyClass的建構函式被呼叫時,它建立巢狀類的例項並且為欄位分配引用,下圖演示了程式碼中物件的結構。
class MyClass { class MyCounter { public int Count{get;private set;} public static MyCounter operator ++(MyCounter current) { current.Count++; return current; } } private MyCounter counter; public MyClass(){counter=new MyCounter();} public int Incr(){return (counter++).Count;} public int GetValue(){return counter.Count;} } class Program { static void Main() { var mc=new MyClass(); mc.Incr();mc.Incr();mc.Incr(); mc.Incr();mc.Incr();mc.Incr(); Console.WriteLine("Total: {0}",mc.GetValue()); } }
可見性和巢狀型別
在第7章中,我們已經瞭解到類和型別通常有public或internal的訪問級別。然而,巢狀型別的不同之處在於,它們有成員訪問級別而不是型別訪問級別。因此,下面的命題是成立的。
- 在類內部宣告的巢狀型別可以有5種類成員訪問級別中的任何一種:public、protected、private、internal或protected internal
- 在結構內部宣告的巢狀型別可以有3種結構成員訪問級別中的任何一種:public、internal或private
在這兩種情況下,巢狀型別的預設訪問級別都是private,也就是說不能被封閉型別以外的物件所見。
封閉類和巢狀類的成員之間的關係是很容易理解的,如下圖所示。不管封閉型別的成員聲明瞭怎樣的訪問級別,包括private和protected,巢狀型別都能訪問這些成員。
然而,它們之間的關係不是對稱的。儘管封閉型別的成員總是可見巢狀型別的宣告並且能建立它的變數及例項,但是它們不能完全訪問巢狀型別的成員。相反,這種訪問許可權受限於巢狀類成員宣告的訪問級別--就好像巢狀型別是一個獨立的型別一樣。也就是說,它們可以訪問public或internal的成員,但是不能訪問巢狀型別的private或protected成員。
我們可以把這種關係總結如下。
- 巢狀型別的成員對封閉型別的成員總是有完全訪問許可權
- 封閉型別的成員
- 總是可以訪問巢狀型別本身
- 只能訪問聲明瞭有訪問許可權的巢狀型別成員
巢狀型別的可見性還會影響基類成員的繼承。如果封閉型別是一個派生類,巢狀型別就可以通過使用相同的名字來隱藏基類成員。可以在巢狀型別的宣告上使用new修飾符來顯式隱藏。
巢狀型別中的this引用指的是巢狀型別的物件--不是封閉型別的物件。如果巢狀型別的物件需要訪問封閉型別,它必須持有封閉型別的引用。如以下程式碼所示,我們可以把封閉物件提供的this引用作為引數傳給巢狀型別的建構函式:
class SomeClass //封閉類 { int Field1=15,Field2=20; //封閉類的欄位 MyNested mn=null; //巢狀類的引用 public void PrintMyMembers() { mn.PrintOuterMembers(); //呼叫巢狀類中的方法 } public SomeClass() //建構函式 { mn=new MyNested(this); //建立巢狀類的例項 } class MyNested //巢狀類宣告 { SomeClass sc=null; //封閉類的引用 public MyNested(SomeClass SC)//巢狀類建構函式 { sc=SC; //儲存巢狀類的引用 } public void PrintOuterMembers() { Console.WriteLine("Field1: {0}",sc.Field1);//封閉欄位 Console.WriteLine("Field2: {0}",sc.Field2);//封閉欄位 } } //巢狀類結束 } class Program { static void Main() { var MySC=new SomeClass(); MySC.PrintOuterMembers(); } }
解構函式和dispose模式
第6章介紹了建立類物件的建構函式。類還可以擁有解構函式(destructor),它可以在一個類的例項不再被引用的時候執行一些操作,以清除或釋放非託管資源。非託管資源是指類似用Win32 API或非託管記憶體塊獲取的檔案控制代碼這樣的資源。使用.NET資源是無法獲取它們的,因此如果我們只用.NET類,是不需要編寫太多解構函式的。
關於解構函式要注意以下幾點。
- 每個類只能有一個解構函式
- 解構函式不能有引數
- 解構函式不能有訪問修飾符
- 解構函式名稱與類名相同,但要在前面加一個波浪符
- 解構函式只能作用於類的例項。因此沒有靜態解構函式
- 不能在程式碼中顯式呼叫析構函教。相反,當垃圾同收器分析程式碼並認為程式碼中不存在指向該物件的可能路徑時,系統會在垃圾回收過程中呼叫解構函式
例如,下面的程式碼通過類Class1演示了解構函式的語法:
Class1 { ~Class1() { CleanupCode } … }
使用解構函式時一些重要的原則如下:
- 不要在不需要時實現解構函式,這會嚴重影響效能
- 解構函式應該只釋放物件擁有的外部資源
- 解構函式不應該訪問其他物件,因為無法認定這些物件是否已經被銷燬
在C#3.0釋出之前,解構函式有時也叫終結器(finalizer)。你可能會經常在文字或.NET API方法名中遇到這個術語。
標準dispose模式
與C++解構函式不同,C#解構函式不會在例項超出作用域時立即呼叫。事實上,你無法知道何時會呼叫解構函式。此外,如前所述,你也不能顯式呼叫解構函式。你所能知道的只是,系統會在物件從託管堆上移除之前的某個時刻呼叫解構函式。
如果你的程式碼中包含的非託管資源越快釋放越好,就不能將這個任務留給解構函式,因為無法保證它會何時執行。相反,你應該採用標準dispose模式。
標準dispose模式包含以下特點。
- 包含非託管資源的類應該實現IDisposable介面,後者包含單一方法Dispose。Dispose包含釋放資源的清除程式碼
- 如果程式碼使用完了這些資源並且希望將它們釋放,應該在程式程式碼中呼叫Dispose方法。注意,這是在你的程式碼中(不是系統中)呼叫Dispose
- 你的類還應該實現一個解構函式,在其中呼叫Dispose方法,以防止之前沒有呼叫該方法。
可能會有點混亂,所以我們再總結一下。你想將所有清除程式碼放到Dispose方法中,並在使用完資源時呼叫。以防萬一Dispose沒有呼叫,類的解構函式也應該呼叫Dispose。而另一方面如果呼叫了Dispose,你就希望通知垃圾回收器不要再呼叫解構函式,因為已經由Dispose執行了清除操作。解構函式和Dispose程式碼應該遵循以下原則。
- 解構函式和Dispose方法的邏輯應該是,如果由於某種原因程式碼沒有呼叫Dispose,那麼解構函式應該呼叫它,並釋放資源
- 在Dispose方法的最後應該呼叫GC.SuppressFinalize方法,通知CLR不要呼叫該物件的解構函式,因為清除工作已經完成
- 在Dispose中實現這些程式碼,這樣多次呼叫該方法是安全的。也就是說程式碼要這樣寫:如果該方法已經被呼叫,那麼任何後續呼叫都不會執行額外的工作,也不會丟擲任何異常
下面的程式碼展示了標準的dispose模式,下圖對其進行了闡釋。這段程式碼的要點如下:
- Dispose方法有兩個過載:一個是public的,一個是protected的。protected的過載包含實際的清除程式碼
- public版本可以在程式碼中顯式呼叫以執行清除工作。它會呼叫protected版本
- 解構函式呼叫protected版本
- protected版本的bool引數通知方法是被解構函式或是其他程式碼呼叫。這一點很重要,因為結果不同所執行的操作會略有不同。細節如下面的程式碼所示
比較建構函式和解構函式
下表對何時呼叫建構函式和解構函式進行了總結和比較。
和COM的互操作
儘管本書不介紹COM程式設計,但是C#4.0專門增加了幾個語法改變,使得COM程式設計更容易。其中的一個改變叫做“省略ref”特性,允許不需要使用方法返回值的情況下,無需ref關鍵字即可呼叫COM方法。
例如,如果程式所在的機器上安裝了微軟Word,你就可以在自己的程式中使用Word的拼寫檢査功能。這個方法是 Microsoft.Office.Tools.Word 名稱空間的Document類中的CheckSpelling方法。這個方法有12個引數,且都是ref引數。也就是說,之前即使你不需要為方法傳入資料或是從方法取回資料,也只能為每一個引數提供一個引用變數。省略ref關鍵字只能用於COM方法, 否則就仍然會收到編譯錯誤。
程式碼差不多應該如下,對於這段程式碼注意幾點。
- 我只使用第二個和第三個引數,都是布林型。但是我不得不建立兩個變數,object型別的ignoreCase和alwaysSuggest來儲存值,因為方法需要ref引數
- 我建立了叫做optional的object變數用於其他10個引數
object ignoreCase=true; object alwaysSuggest=false; object optional=Missing.Value; tempDoc.CheckSpelling(ref optional,ref ignoreCase,ref alwaysSuggest, ref optional,ref optional,ref optional,ref optional,ref optional, ref optional,ref optional,ref optional,ref optional);
有了“省略ref”特性,我們的程式碼就乾淨多了,因為對於不需要輸出的引數,我們不再需要使用ref關鍵字,只需要為我們關心的兩個引數使用內聯的bool。簡化後的程式碼如下:
object optional=Missing.Value; tempDoc.CheckSpelling(optional,true,false, optional,optional,optional,optional,optional, optional,optional,optional,optional);
除了“省略ref”特性,對於可選的引數我們可以使用C#4.0的可選引數特性,比之前的又簡單很多,如下所示:
tempDoc.CheckSpelling( Missing.Value, true, false );
如下程式碼是一個包含這個方法的完整程式。要編譯這段程式碼,你需要在本機上安裝 Visual Studio Tools for Office(VSTO)並且必須為專案新增 Microsoft.Office.Interop.Word 程式集的引用。要執行這段編譯的程式碼,必須在本機上安裝 Microsoft Word。
using System; using System.Reflection; using Microsoft.Office.Interop.Word; class Program { static void Main() { Console.WriteLine("Enter a string to spell-check"); string stringToSpellCheck=Console.ReadLine(); string spellingResults; int errors=0; if(stringToSpellCheck.Length==0) { spellingResults="No string to check"; } else { Microsoft.Office.Interop.Word.Application app= new Microsoft.Office.Interop.Word.Application(); Console.WriteLine("\nChecking the string for misspellings …"); app.Visible=false; Microsoft.Office.Interop.Word._Document tempDoc=app.Document.Add(); tempDoc.Words.First.InsertBefore(stringToSpellCheck); Microsoft.Office.Interop.Word.ProofreadingErrors spellErrorsColl= tempDoc.SpellingErrors; errors=spellErrorsColl.Count; // 1.不是用可選引數 // object ignoreCase=true; // object alwaysSuggest=false; // object optional=Missing.Value; // tempDoc.CheckSpelling(ref optional,ref ignoreCase,ref alwaysSuggest, // ref optional,ref optional,ref optional,ref optional,ref optional, // ref optional,ref optional,ref optional,ref optional); // 2.使用C#4.0的“省略ref”特性 object optional=Missing.Value; tempDoc.CheckSpelling(optional,true,false, optional,optional,optional,optional,optional, optional,optional,optional,optional); //3.使用“省略ref”和可選引數特性 app.Quit(false); spellingResults=errors+" errors found"; } Console.WriteLine(spellingResults); Console.WriteLine("\nPress <Enter> to exit program"); Console.WriteLine(); } }
如果你執行這段程式碼,會得到如圖25-8所示的一個控制檯視窗,它會要求你輸入希望進行拼寫檢査的字串。在收到宇符串之後它會開啟Word然後執行拼寫檢査。此時,你會看到出現了一個Word的拼寫檢査視窗,如圖25-9所示。
from: http://www.cnblogs.com/moonache/p/7577098.html