在本章中,我會介紹使用C#時的一些重要而又不適合放到其餘章節的主題,包括字符串操做、可空類型、Main方法、文檔註釋以及嵌套類型。編程
對於內部計算來講0和1很適合,可是對於人類可讀的輸入和輸出,咱們須要字符串。BCL提供了不少能讓字符串操做變得更簡單的類。
C#預約義的string類型表明了.NET的System.String類。對於字符串,最須要理解的概念以下。數組
string類型有不少有用的字符串操做成員,包括容許咱們進行諸如檢測長度、改變大小寫、鏈接字符串等一些有用的操做。下表列出了其中一些最有用的成員。
從上表中的大多數方法的名字來看,好像它們都會改變字符串對象。其實,它們不會改變字符串而是返回了新的副本。對於一個string,任何「改變」都會分配一個新的恆定字符串。
例如,下面的代碼聲明並初始化了一個叫作s的字符串。第一個WriteLine語句調用了s的ToUpper方法,它返回了字符串中全部字母爲大寫形式的副本。最後一行輸出了s的值,能夠看到,字符串並無改變。安全
string s = "Hi there."; Console.WriteLine("{0}",s.ToUpper()); //輸出全部字母爲大寫的副本 Console.WriteLine("{0}", s); //字符串沒有變
筆者本身編碼時,發現上表中頗有用的一個方法是Split。它將一個字符串分隔爲若干子字符串,並將它們以數組的形式返回。將一組按預約位置分隔字符串的分隔符傳給Split方法,就能夠指定如何處理輸出數組中的空元素。固然,原始字符串依然不會改變。
下面的代碼顯示了一個使用Split方法的示例。在這個示例中,分隔符由空字符和4個標點符號組成。app
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類型的字符串,而後輸出了它的值。第四行代碼經過替換初始字符串的一部分改變了其實際對象。當輸出它的值,隱式調用ToString時,咱們能夠看到,和string類型的對象不一樣,StringBuilder對象確實被修改了。ide
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是靜態的,因此咱們須要經過目標類型名來調用它。ui
double dl = double.Parse("25.873"); ↑ ↑ 目標類型 要轉換的字符串
如下代碼給出了一個把兩個字符串解析爲double型值並把它們相加的示例:this
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須要知道的亟要事項以下。
以下代碼演示了使用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;
有了這樣的聲明語句,編譯器就會產生可空類型並關聯變量類型。可空類型的結構以下圖所示。
使用可空類型基本與使用其餘類型的變量同樣。讀取可空類型的變量返回其值。可是你必須確保變量不是null的,嘗試讀取一個null的變量會產生異常。
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
能夠將如下三種類型的值賦給可空類型的變量:
如下代碼分別給出了三種類型賦值的示例:
int? myI1,myI2,myI3 myI1 = 28; //基礎類型的值 myI2 = myI1; //可空類型的值 myI3 = null; //null Console.WriteLine("myI1: {0}, myI2: {1}", myI1, myI2);
標準算術運算符和比較運算符一樣也能處理可空類型。還有一個特別的運算符叫作空接合運算符(null coalescing operator),它容許咱們在可空類型變量爲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的結構(值類型),它有兩個公共字段。
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();
每個C#程序都必須有一個入口點--一個必須叫作Main的方法。
在貫穿本書的示例代碼中,都使用了一個不接受參數而且也不返回值的Main方法。然而,一共有4種形式的Main能夠做爲程序的入口點。這些形式以下:
前面兩種形式在程序終止後都不返回值給執行環境。後面兩種形式則返回int值。若是使用返回值,一般用於報告程序的成功或失敗,0一般用於表示成功。
第二種和第四種形式容許咱們在程序啓動時從命令行向程序傳入實參,也叫作參數。命令行參數的一些重要特性以下。
例如,下面叫作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的4種可用入口點形式中的一種聲明。固然,若是你聲明其餘方法的名稱爲Main,只要它們不是4種入口點形式中的一種就是合法--可是,這樣作是很是容易混淆的。
Main的可訪問性
Main能夠被聲明爲public或private。
然而,不管Main聲明的訪問級或所屬類或結構的訪問級別是什麼,執行環境老是能訪問Main。
默認狀況下,當Visual Studio建立了一個項目時,它就建立了一個程序框,其中的Main是隱式private。若是須要,你隨時能夠添加public修飾符。
文檔註釋特性容許咱們以XML元素的形式在程序中包含文檔(第19章介紹XML)。Visual Studio會幫助咱們插入元素,以及從源文件中讀取它們並複製到獨立的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 {…}
在以前的示例中,咱們看到了summay XML標籤的使用。C#可識別的標籤還有不少。下表列出了最重要的一些。
咱們一般直接在命名空間中聲明類型。然而,咱們還能夠在類或結構中聲明類型。
例如,如下代碼顯示了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的訪問級別。然而,嵌套類型的不一樣之處在於,它們有成員訪問級別而不是類型訪問級別。所以,下面的命題是成立的。
在這兩種狀況下,嵌套類型的默認訪問級別都是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(); } }
第6章介紹了建立類對象的構造函數。類還能夠擁有析構函數(destructor),它能夠在一個類的實例再也不被引用的時候執行一些操做,以清除或釋放非託管資源。非託管資源是指相似用Win32 API或非託管內存塊獲取的文件句柄這樣的資源。使用.NET資源是沒法獲取它們的,所以若是咱們只用.NET類,是不須要編寫太多析構函數的。
關於析構函數要注意如下幾點。
例如,下面的代碼經過類Class1演示了析構函數的語法:
Class1 { ~Class1() { CleanupCode } … }
使用析構函數時一些重要的原則以下:
在C#3.0發佈以前,析構函數有時也叫終結器(finalizer)。你可能會常常在文本或.NET API方法名中遇到這個術語。
與C++析構函數不一樣,C#析構函數不會在實例超出做用域時當即調用。事實上,你沒法知道什麼時候會調用析構函數。此外,如前所述,你也不能顯式調用析構函數。你所能知道的只是,系統會在對象從託管堆上移除以前的某個時刻調用析構函數。
若是你的代碼中包含的非託管資源越快釋放越好,就不能將這個任務留給析構函數,由於沒法保證它會什麼時候執行。相反,你應該採用標準dispose模式。
標準dispose模式包含如下特色。
可能會有點混亂,因此咱們再總結一下。你想將全部清除代碼放到Dispose方法中,並在使用完資源時調用。以防萬一Dispose沒有調用,類的析構函數也應該調用Dispose。而另外一方面若是調用了Dispose,你就但願通知垃圾回收器不要再調用析構函數,由於已經由Dispose執行了清除操做。析構函數和Dispose代碼應該遵循如下原則。
下面的代碼展現了標準的dispose模式,下圖對其進行了闡釋。這段代碼的要點以下:
下表對什麼時候調用構造函數和析構函數進行了總結和比較。
儘管本書不介紹COM編程,可是C#4.0專門增長了幾個語法改變,使得COM編程更容易。其中的一個改變叫作「省略ref」特性,容許不須要使用方法返回值的狀況下,無需ref關鍵字便可調用COM方法。
例如,若是程序所在的機器上安裝了微軟Word,你就能夠在本身的程序中使用Word的拼寫檢査功能。這個方法是 Microsoft.Office.Tools.Word 命名空間的Document類中的CheckSpelling方法。這個方法有12個參數,且都是ref參數。也就是說,以前即便你不須要爲方法傳入數據或是從方法取回數據,也只能爲每個參數提供一個引用變量。省略ref關鍵字只能用於COM方法, 不然就仍然會收到編譯錯誤。
代碼差很少應該以下,對於這段代碼注意幾點。
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所示。
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">