版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第4章高級(jí)C#概念4.1集合
4.2異常
4.3屬性
4.4索引器
4.5委托
4.6事件
4.7綜合案例:處理SARS緊急事件本章小結(jié)
練習(xí)與作業(yè)
上機(jī)部分(四)
學(xué)習(xí)目標(biāo)●使用集合●理解異常處理機(jī)制●使用屬性●使用索引器●理解委托和事件機(jī)制及其使用4.1集合
.NET框架的基類庫提供了一組“集合”,這是一組通用的數(shù)據(jù)類型。這些數(shù)據(jù)類型為對(duì)數(shù)據(jù)進(jìn)行集中的存儲(chǔ)和操作提供了可能。在System.Collections命名空間中包含了許多接口和類,它們定義了對(duì)象的各種集合,如列表、隊(duì)列、數(shù)組和字典等。表4-1顯示了System.Collections命名空間中的一些集合類。要想學(xué)習(xí)這些類的使用,可以逐個(gè)去研究這些類的方法和屬性,但是更好的學(xué)習(xí)方法是先了解這些集合類可實(shí)現(xiàn)的基本接口。我們知道,System.Collection命名空間下也定義了一些接口,每個(gè)接口都代表了集合的某個(gè)特定功能或特性。表4-2顯示了System.Collection命名空間定義的接口。要想成為一個(gè)“合格”的集合類,必須去全部或部分實(shí)現(xiàn)這些接口,以滿足集合的特性。下面我們就先從幾個(gè)典型的接口入手,看看它們是如何對(duì)集合類進(jìn)行約束的。表4-1System.Collections命名空間中的集合類表4-2System.Collections下的接口4.1.1ICollection、IList和IDictionary接口
1.ICollection和枚舉器
ICollection接口定義了類似集合的對(duì)象所應(yīng)具備的最基本的特性,所有的集合類都能夠?qū)崿F(xiàn)這個(gè)接口。ICollection接口公開了一個(gè)只讀的Count屬性和CopyTo方法,該方法能夠把集合對(duì)象中的元素復(fù)制給數(shù)組。此外,ICollection接口還實(shí)現(xiàn)了IEnumerable接口,IEnumerable接口定義了GetEnumerator()方法,該方法返回一個(gè)枚舉器對(duì)象。枚舉器可以用來讀取集合中的數(shù)據(jù),但是不能修改這些數(shù)據(jù)。我們常用的foreach循環(huán)支持枚舉器。這樣一來,所有的集合類都可以通過實(shí)現(xiàn)這個(gè)接口獲得一個(gè)枚舉器對(duì)象,當(dāng)然我們也可以使用foreach循環(huán)達(dá)到快速遍歷的效果。
注意:枚舉器和枚舉兩個(gè)術(shù)語很容易混淆,但它們是完全不同的概念。枚舉是一種命名常量的特殊類型,如:enumColor{Red,Green,Blue}
我們可以使用IEnumerator接口操縱枚舉器對(duì)象。IEnumerator接口中的屬性和方法見表4-3。最初,枚舉器被定位于集合的第一個(gè)元素之前。Reset也是將枚舉器返回到此位置。在創(chuàng)建枚舉器或調(diào)用Reset方法后,如果要讀取Current的值,就應(yīng)該調(diào)用MoveNext方法將枚舉器移動(dòng)到集合的第一個(gè)元素。當(dāng)越過集合的末尾之后,枚舉器將返回?zé)o效狀態(tài)。這時(shí)調(diào)用MoveNext方法將會(huì)返回false。此時(shí)調(diào)用Current將會(huì)得到異常。表4-3
IEnumerator接口中的屬性和方法【例4-1】下面的代碼顯示了如何遍歷一個(gè)數(shù)組。關(guān)于集合的遍歷與此類似。
staticvoidMain(){int[]arr=newint[]{1,2,3};IEnumeratore=arr.GetEnumerator();while(e.MoveNext())Console.WriteLine("number:{0}",e.Current);}
程序運(yùn)行結(jié)果如圖4-1所示。圖4-1例4-1運(yùn)行結(jié)果
如果一個(gè)集合類支持枚舉器訪問,那么我們也可以使用foreach的方法達(dá)到快速遍歷的效果。上面的代碼也可以寫成:
staticvoidMain(){int[]arr=newint[]{1,2,3};foreach(intxinarr)Console.WriteLine("number:{0}",x);}2.IList和IDictionary接口
IList接口表示可單獨(dú)索引的有序?qū)ο蠹?。Array、ArrayList、StringCollection等類實(shí)現(xiàn)了這個(gè)接口。IList接口繼承自ICollection接口,除此之外,它還定義了自己的屬性和方法。表4-4總結(jié)了IList接口中的屬性和方法。表4-4List接口中的屬性和方法IDictionary是一個(gè)關(guān)聯(lián)鍵/值對(duì)的集合的接口。每個(gè)關(guān)聯(lián)必須具有一個(gè)唯一的非空引用鍵,但關(guān)聯(lián)的值可以是任何類型的對(duì)象,包括null。SortedList、Hashtable和DictionaryBase是實(shí)現(xiàn)了該接口的集合類。
IDictionary接口的實(shí)現(xiàn)分為以下幾種類型:
(1)只讀,不能修改只讀的IDictionary。
(2)固定大小,固定大小的IDictionary不允許添加和刪除元素,但可以修改現(xiàn)有元素。
(3)可變大小,可變大小的IDictionary允許添加、修改和刪除元素。表4-5總結(jié)了IDictionary接口中的成員。表4-5IDictionary接口中的成員4.1.2ArrayList類
ArrayList是實(shí)現(xiàn)了IList接口的集合類,我們可以將其視作Array和Collection對(duì)象的混合體。對(duì)于該類中的元素,既可以像處理數(shù)組一樣通過元素索引確定其地址,對(duì)元素進(jìn)行排序和搜索,也可以象處理集合那樣添加一個(gè)元素,在給定位置插入元素或刪除元素。
ArrayList和Array之間的區(qū)別如下:
Array是抽象類,所有的數(shù)組均派生自此類;ArrayList是一個(gè)普通類,可以被實(shí)例化。
在Array中一次只能獲取或設(shè)置一個(gè)元素的值;ArrayList則提供了操作元素的一系列方法,包括插入、刪除和添加等。
?ArrayList具有Capacity屬性。使用此屬性可以使ArrayList的容量自動(dòng)擴(kuò)展。如果屬性ArrayList.Capacity的值被修改,則會(huì)自動(dòng)進(jìn)行內(nèi)存的重新分配和元素的復(fù)制。這樣,當(dāng)有元素被添加到ArrayList中時(shí),其容量的上限會(huì)動(dòng)態(tài)增加。而Array的容量是固定的,因而不會(huì)被修改。
可以設(shè)置Array的下限,而ArrayList的下限始終為0。
Array可以有多維,而ArrayList只有一維。
【例4-2】
下面的代碼顯示了如何使用ArrayList類,添加元素和遍歷。
usingSystem;usingSystem.Collections;classClass2{ staticvoidMain(string[]args) { ArrayListmyal=newArrayList(); //創(chuàng)建ArrayList對(duì)象
myal.Add("歡迎"); //添加元素
myal.Add("使用"); myal.Add("C#"); myal.Add("!"); Console.WriteLine("\n\t我的表單"); Console.WriteLine("數(shù)量:{0}",myal.Count); Console.WriteLine("容量:{0}",myal.Capacity); Console.Write("內(nèi)容:"); IEnumeratormyErtor=myal.GetEnumerator(); //獲得枚舉器while(myErtor.MoveNext()) //遍歷元素
Console.Write("{0}",myErtor.Current); Console.WriteLine(); }}
上述示例的運(yùn)行結(jié)果如圖4-2所示。注意,ArrayList的數(shù)量和容量往往是不一致的。ArrayList的初始容量是16,當(dāng)數(shù)量超過ArrayList的容量時(shí),其容量Capacity會(huì)增長(zhǎng)一倍。此外,該示例中還使用了IEnumerator接口來訪問ArrayList中的每一個(gè)元素,這是因?yàn)锳rrayList集合實(shí)現(xiàn)了IEnumerable接口,并實(shí)現(xiàn)了它的唯一方法GetEnumerator。
GetEnumerator方法返回一個(gè)實(shí)現(xiàn)了IEnumerator接口的遍歷器,該遍歷器用于訪問ArrayList集合中的每一個(gè)元素。圖4-2例4-2運(yùn)行結(jié)果4.1.3SortedList類
SortedList類實(shí)現(xiàn)了IDictionary接口,該類在內(nèi)部維護(hù)了兩個(gè)動(dòng)態(tài)數(shù)組,一個(gè)用于存放鍵,另一個(gè)用于存放相關(guān)聯(lián)的值,SortedList對(duì)象中的每一個(gè)元素都是一個(gè)鍵/值對(duì)。注意,無論何時(shí),SortedList都不允許有重復(fù)的鍵。
Count屬性用來獲取SortedList所包含的元素個(gè)數(shù),Add方法用來向SortedList添加一個(gè)元素。
【例4-3】下面的代碼顯示了如何使用SortedList,添加元素和遍歷。
usingSystem;usingSystem.Collections;classClass1{ staticvoidMain(string[]args) { SortedListmysl=newSortedList(); //創(chuàng)建SortedList對(duì)象
mysl.Add("First",1); //添加元素
mysl.Add("Third",3); mysl.Add("Second",2); Console.WriteLine("Count:{0}",mysl.Count); Console.WriteLine("\t-KEY-\t-VALUE-"); for(inti=0;i<mysl.Count;i++) //遍歷,打印鍵和值
{Console.WriteLine("\t{0}:\t{1}",mysl.GetKey(i),mysl.GetByIndex(i)); } }}
上述示例的運(yùn)行結(jié)果如圖4-3所示。我們可以發(fā)現(xiàn),SortedList會(huì)即時(shí)保持元素的排序狀態(tài),所以在SortedList上的操作要比其他的集合類慢。這是一種典型的犧牲存儲(chǔ)速度而提高訪問速度的例子。注意:只有當(dāng)編程確實(shí)需要較高的靈活性時(shí),才應(yīng)當(dāng)使用SortedList對(duì)象。圖4-3例4-3運(yùn)行結(jié)果4.2異常異常是程序運(yùn)行時(shí)產(chǎn)生的錯(cuò)誤,這種錯(cuò)誤和編譯錯(cuò)誤不同,一般很難在編譯時(shí)被發(fā)現(xiàn),而是在程序運(yùn)行時(shí)方產(chǎn)生。下面的代碼在編譯時(shí)沒有問題,但當(dāng)我們點(diǎn)擊【運(yùn)行】按鈕時(shí)將在第2行彈出圖4-4所示的異常窗口。
VS.NET2005幫助我們?cè)陂_發(fā)程序時(shí)很清楚地定位異常產(chǎn)生的原始代碼。然而異常的產(chǎn)生往往是不可預(yù)見的,若在開發(fā)環(huán)節(jié)沒有捕獲異常,則發(fā)布以后的程序仍然不是一個(gè)好的程序,當(dāng)最終用戶不小心觸發(fā)了潛藏的異常時(shí),程序?qū)?huì)發(fā)生崩潰,如圖4-5所示。圖4-4異常的產(chǎn)生圖4-5發(fā)生崩潰時(shí)的對(duì)話框
那么,如何捕獲這些異常錯(cuò)誤,使程序能最大限度的安全呢?C#采用結(jié)構(gòu)化的異常處理語句,使我們能夠以標(biāo)準(zhǔn)化和可控制的方式處理運(yùn)行時(shí)錯(cuò)誤。而在傳統(tǒng)的解決方案中,方法失敗時(shí)必須返回錯(cuò)誤代碼,而且每次調(diào)用方法時(shí)都必須手動(dòng)檢驗(yàn)這些值。此過程不但冗長(zhǎng),而且容易出錯(cuò)。比如,針對(duì)上面的代碼,如果我們作出這樣的改動(dòng),顯然不是最佳的解決方法。inti1=0;boolflag=true;if(i1==0){flag=false;return;}intres=5/i1;Console.WriteLine(res.ToString());
因此,要想編寫健壯的程序,上述簡(jiǎn)單的錯(cuò)誤處理技術(shù)是不能勝任的。這就體現(xiàn)了當(dāng)錯(cuò)誤產(chǎn)生時(shí)由運(yùn)行庫自己生成異常的重要性。這些異常提供了關(guān)于錯(cuò)誤的詳細(xì)信息。4.2.1System.Exception
在C#中,異常用類來表示。.NET框架提供了大量的異常類,這些類存儲(chǔ)了各種異常的相關(guān)信息和幫助。異常類的層次結(jié)構(gòu)如圖4-6所示。圖4-6異常類的層次結(jié)構(gòu)
如圖4-6所示,所有的異常類都必須從內(nèi)部異常類Exception派生而來,而Exception是System命名空間的一部分。因此,所有的異常類都是Exception類的子類。例如,當(dāng)試圖除以0時(shí),系統(tǒng)將產(chǎn)生DividedByZeroException異常,該異常是算術(shù)異常ArithmeticException的直接派生類。
C#的異常處理機(jī)制規(guī)定,一旦出現(xiàn)運(yùn)行時(shí)錯(cuò)誤,系統(tǒng)將會(huì)產(chǎn)生一個(gè)包含該錯(cuò)誤信息的異常對(duì)象。該對(duì)象一旦產(chǎn)生,就會(huì)由相應(yīng)的異常處理程序捕獲并進(jìn)行處理。表4-6描述了一些Exception基類的屬性,它們可以幫助你理解代碼位置、類型和異常的發(fā)生原因。任何繼承自該基類的異常都將提供這些屬性。表4-6System.Exception的屬性Exception對(duì)象在?.NET框架中定義,完整的名字是System.Exception。但是一般情況下,.NET的類和應(yīng)用程序不拋出這種原始異常??蚣芏x了另兩種功能相同的類:System.SystemException和System.ApplicationException。幾乎所有?.NET框架中定義的異常對(duì)象都繼承自SystemException,然而自定義的或是應(yīng)用程序指定的異常處理對(duì)象應(yīng)該繼承自ApplicationException。這兩種類雖然沒有給Exception基類增加任何屬性或方法,但它們提供了一種對(duì)異常進(jìn)行分類的有效方法。表4-7列出了一些最常用的系統(tǒng)異常類。例如,數(shù)學(xué)操作可能拋出ArithmeticException或DivideByZeroException對(duì)象,而函數(shù)可能拋出ArgumentOutOfRangeException對(duì)象。若要獲得完整的異常對(duì)象列表,可以使用對(duì)象瀏覽器中的搜索命令并尋找Exception子串。表4-7常用的系統(tǒng)異常類
下面介紹上述異常對(duì)象共有的一些重要屬性和方法(注意,除了Source和HelpLink外,其他所有屬性都是只讀的)。
Message屬性是用來描述異常的文本。例如,DivideByZeroException的Message屬性返回字符串“Attemptedtodividebyzero”。Exception對(duì)象從System.Object中重寫了ToString()方法,并返回一條錯(cuò)誤消息,該消息與將在對(duì)話框中顯示給最終用戶的錯(cuò)誤消息相同。這一點(diǎn)它與Message屬性類似,但它還包括了模塊名。如果調(diào)試信息已經(jīng)嵌入到可執(zhí)行代碼中,該方法還會(huì)返回過程名和錯(cuò)誤發(fā)生的準(zhǔn)確行號(hào):
System.DivideByZeroException:Attemptedtodividebyzero.atMyApplication.Form1.TestProcinC:\MyApplication\Form1.cs:line70TargetSite屬性返回異常第一次拋出時(shí)過程的過程名和簽名,用C#語言表示:
Int32DivideNumber(Int32x,Int32y)StackTrace屬性返回一個(gè)字符串,用來描述棧的路徑,即從異常最初拋出的位置(也就是說,錯(cuò)誤發(fā)生的位置)到錯(cuò)誤被捕獲的位置的路徑。例如,假設(shè)TestProc過程調(diào)用EvalResult過程,后者隨后調(diào)用DivideNumber函數(shù),并假設(shè)后兩個(gè)過程沒有捕獲到異常。如果最內(nèi)部的DivideNumber函數(shù)拋出DivideByZeroException,在TestProc過程中讀出的StackTrace屬性的值將如下所示:
atMyApplication.Form1.DivideNumber(Int32x,Int32y) inC:\MyApplication\Form1.cs:line91atMyApplication.Form1.EvalResult()in C:\MyApplication\Form1.cs:line87atMyApplication.Form1.TestProc()inC:\MyApplication\Form1.cs:line77
只有當(dāng)可執(zhí)行文件中包含了調(diào)試信息時(shí),才能獲得這些詳細(xì)信息。如果在Release模式下編譯程序,將看不到源文件的文件名和行號(hào)。顯然,StackTrace屬性是了解拋出異常時(shí)究竟發(fā)生了什么的最好途徑。
Source屬性用來設(shè)置或返回拋出異常的組件名。對(duì)當(dāng)前應(yīng)用程序中拋出的異常,這個(gè)屬性將返回空的字符串。HelpLink屬性設(shè)置或返回UniformResourceName(URN)或UniformResourceLocator(URL),這些鏈接指向有關(guān)Exception對(duì)象的幫助文件,如下所示:
file://C:/MyApplication/manual.html#ErrorNum42
掌握異常處理機(jī)制需要注意四個(gè)關(guān)鍵字:try、catch、throw、finally。它們組合成了各種特殊的程序結(jié)構(gòu),下面我們來討論這些關(guān)鍵字和結(jié)構(gòu)的用法。4.2.2try和catch塊異常處理的中心是try和catch。這兩個(gè)關(guān)鍵字通常組合在一起使用。try/catch異常處理模塊的語法如下:
try{ //程序代碼
}catch(Exceptione){ //錯(cuò)誤處理代碼
}
從上面的語法可以看出,C#異常處理機(jī)制將程序代碼和錯(cuò)誤處理代碼分隔開來,程序代碼寫在try語句塊中,錯(cuò)誤處理代碼寫在catch語句塊中。catch塊負(fù)責(zé)捕捉try塊產(chǎn)生的異常。
例如,圖4-4中的除法語句可以放在try塊中,而不需要任何的判斷語句。當(dāng)出現(xiàn)除0異常時(shí),將交由catch塊處理。
【例4-4】
下面的代碼顯示了如何使用try和catch語句。
staticvoidMain(){inti1=0,res=0;Console.WriteLine("請(qǐng)輸入一個(gè)數(shù):");try{i1=int.Parse(Console.ReadLine());res=5/i1;}catch(DivideByZeroExceptione){Console.WriteLine(e.ToString());}Console.WriteLine("res={0}",res.ToString());}
上述代碼中,當(dāng)用戶輸入字符“0”時(shí),在try結(jié)構(gòu)中將產(chǎn)生一個(gè)除0異常,這個(gè)異常將會(huì)被catch捕捉。如果我們知道確切的異常類型,可以在catch中指明捕捉的是哪種異常。程序運(yùn)行結(jié)果如圖4-7所示。圖4-7例4-4運(yùn)行結(jié)果
注意:一般來說,我們不在try結(jié)構(gòu)中對(duì)變量進(jìn)行聲明,因?yàn)榇藭r(shí)該變量的有效范圍只是塊級(jí)別的。有時(shí),一個(gè)操作可能產(chǎn)生不同類型的異常,如上述代碼中,若用戶不小心輸入字母o,則會(huì)產(chǎn)生另一種異常。這時(shí),我們可以使用多個(gè)catch塊的結(jié)構(gòu)。
try{ //程序代碼
}catch(DividedByZeroExceptione){ //錯(cuò)誤處理代碼1}catch(FormatExceptione){ //錯(cuò)誤處理代碼2}
那么,有沒有一種結(jié)構(gòu)可以捕獲所有的異常呢?考慮到異常的層次結(jié)構(gòu),可以采用下面這種方式。
try{ //程序代碼
}catch(Exceptione){ //錯(cuò)誤處理代碼
}
這種結(jié)構(gòu)稱為常規(guī)catch塊。注意,一個(gè)try塊只能有一個(gè)常規(guī)catch塊,否則將會(huì)出現(xiàn)編譯錯(cuò)誤。還要注意,在含有常規(guī)catch塊的多層catch結(jié)構(gòu)中,始終按從最特定到最不特定的順序?qū)atch塊中的異常排序。此方法在將特定異常傳遞給更常規(guī)的catch塊之前處理該異常。4.2.3使用throw引發(fā)異常前面已經(jīng)談到,若在try塊中出現(xiàn)異常,系統(tǒng)將隱式拋出異常,并由隨后的catch塊捕捉并處理。那么,有沒有一種語法能將異常顯式地拋給程序并進(jìn)行處理呢?我們可以使用throw關(guān)鍵字。throw命令只有一個(gè)參數(shù),即要拋出的異常對(duì)象。因此必須創(chuàng)建這類對(duì)象并按要求設(shè)置它的屬性。在大多數(shù)情況下,可以創(chuàng)建異常對(duì)象,然后在語句中拋出。下面的代碼演示了throw的使用。如果用戶輸入的是1~100范圍以外的一個(gè)數(shù)字,那么就拋出一個(gè)自定義的異常。
…//自定義異常
intuserinput=int.Parse(Console.ReadLine());if(userinput>100||userinput<1) thrownewInvalidNumberInputException(userinput+"不是有效輸入");
在throw語句中,使用new創(chuàng)建了InvalidNumberInputException對(duì)象。請(qǐng)記住,throw拋出對(duì)象,而不是異常類型。在這里,InvalidNumberInputException是一個(gè)用戶自定義異常,也可以直接使用系統(tǒng)提供的異常類型。如果拋出的異常沒有被捕獲,則程序?qū)?huì)出現(xiàn)致命錯(cuò)誤。因此在拋出異常時(shí)應(yīng)確保該異常一定會(huì)被捕獲。若要捕獲所有可能異常的一部分,并把剩下的委派給調(diào)用者,throw語句就特別有用。這是非常普遍的編程方式:代碼的每個(gè)部分都處理它們知道該如何解決的錯(cuò)誤,并將其他的錯(cuò)誤留給調(diào)用代碼。正如前面說明過的,如果沒有catch表達(dá)式捕獲當(dāng)前異常,異常會(huì)自動(dòng)拋給調(diào)用者。但使用顯式完成拋出動(dòng)作是好的習(xí)慣,因?yàn)檫@樣更清楚地表明你是一個(gè)嚴(yán)謹(jǐn)?shù)拈_發(fā)人員。下列代碼演示了這種編程方式。try{ //程序代碼}catch(DividedByZeroExceptione){ //錯(cuò)誤處理代碼1}catch(FormatExceptione){ //錯(cuò)誤處理代碼2}catch(Exceptione){ //顯式拋出這個(gè)未處理的異常給調(diào)用者
throw;}
在上面的代碼段中,不帶參數(shù)的throw語句重新拋出當(dāng)前異常,這種形式的throw語句必須出現(xiàn)在catch結(jié)構(gòu)中才有效。在這種情況下,該語句重新引發(fā)當(dāng)前正由該catch塊處理的那個(gè)異常并由該語句塊的上級(jí)調(diào)用端捕獲。與有參數(shù)情況的區(qū)別是,后者還重置了異常對(duì)象的StackTrace屬性(就像創(chuàng)建了一個(gè)新的異常)。所以對(duì)于重新拋出相同的異常,不帶參數(shù)更好,因?yàn)樗茏屨{(diào)用程序正確地確定異常發(fā)生位置。
【例4-5】下面的代碼演示了如何向上拋出一個(gè)異常并進(jìn)行捕獲。
usingSystem;publicclassThrowTest{staticvoidMain(){try{OutSide();}catch(Exceptione){Console.WriteLine(e.StackTrace);}}publicstaticvoidOutSide(){try{InSide();}catch{throw;}}publicstaticvoidInSide(){thrownewException("throwbyInSide");}}
在上面的代碼中,Main調(diào)用OutSide方法,OutSide方法調(diào)用InSide方法,InSide方法拋出一個(gè)異常被OutSide捕捉,并使用throw將其重新拋出。這個(gè)異常最終由它的上級(jí)調(diào)用者M(jìn)ain捕獲。程序的輸出如圖4-8所示,注意此時(shí)異常的StackTrace屬性已經(jīng)重置了。圖4-8異常的StackTrace信息4.2.4自定義異常對(duì)象如上一節(jié)中的代碼所示,可以使用throw命令創(chuàng)建一個(gè)新的System.Exception對(duì)象(或繼承自System.Exception的對(duì)象)并設(shè)置它的Message屬性,但不能實(shí)現(xiàn)更進(jìn)一步的功能。有時(shí)需要完整地創(chuàng)建一個(gè)新的異常對(duì)象,根據(jù)?.NET規(guī)則,新對(duì)象應(yīng)該繼承自System.ApplicationException(與繼承自System.SystemException的?.NET運(yùn)行時(shí)環(huán)境異常相反)。下面的代碼將會(huì)展示如何創(chuàng)建繼承自System.ApplicationException的類,并用構(gòu)造函數(shù)對(duì)異常的Message屬性賦值?!纠?-6】
下面的代碼演示了如何自定義異常對(duì)象。
//如何拋出自定義異常
usingSystem; publicclassThrow { staticvoidMain() { try { Console.WriteLine("即將拋出異常"); thrownewmyOwnException("這是我自己的異常"); }catch(myOwnExceptione) { Console.WriteLine(e.Message); } Console.WriteLine("繼續(xù)執(zhí)行"); } } publicclassmyOwnException:ApplicationException //自定義異常類
{ publicmyOwnException():base(){} //調(diào)用基類無參構(gòu)造函數(shù)
publicmyOwnException(stringmessage):base(message){} //調(diào)用基類有參構(gòu)造函數(shù)
}
程序的運(yùn)行結(jié)果如圖4-9所示。自定義異常對(duì)象除了能報(bào)告自定義的錯(cuò)誤消息外還有很多其他用途。比如,它們可以包括用來解決錯(cuò)誤(或至少嘗試這么做)的自定義方法。舉例說明:用戶可能想設(shè)計(jì)一個(gè)DriveNotReady-Exception類,其中包含一個(gè)名為ShowMessage的方法用來顯示錯(cuò)誤信息,并請(qǐng)求在驅(qū)動(dòng)器中插入光盤然后重試操作??梢詫⑦@些代碼放在異常類中,使它們的可重用性更好。圖4-9例4-6運(yùn)行結(jié)果4.2.5使用finally
異常處理必須考慮的一個(gè)問題是,異常的發(fā)生可能引起當(dāng)前方法的中斷,使其過早返回。然而,此方法或許已經(jīng)打開了需要關(guān)閉的文件或數(shù)據(jù)連接,若未及時(shí)釋放這些資源,將帶來一系列問題。C#?提供了一個(gè)可選的finally塊。如果在該塊內(nèi)指定了語句,無論控制流如何,都會(huì)執(zhí)行這些語句。圖4-10說明了其運(yùn)行流程。圖4-10異常的運(yùn)行流程【例4-7】
下面的代碼演示了如何使用finally語句。
staticvoidMain() { intdivident=50; intuserInput; intquotient=0; Console.WriteLine("請(qǐng)輸入一個(gè)數(shù)字:"); try { userInput=Convert.ToInt32(Console.ReadLine()); //將用戶輸入轉(zhuǎn)換成整數(shù)
quotient=divident/userInput; } catch(System.FormatExceptionexcepE) //格式異常{ Console.WriteLine(excepE.Message); } catch(System.DivideByZeroExceptionexcepE) //除0異常
{ Console.WriteLine(excepE.Message); } catch(System.OverflowExceptionexcepE) //溢出異常
{ Console.WriteLine(excepE.Message); }finally { if(quotient!=0) { Console.WriteLine("商為{0}",quotient); } } }
在try塊中有兩個(gè)語句,因?yàn)橛锌赡艹霈F(xiàn)下列錯(cuò)誤:●用戶可能輸入字符串;●用戶可能輸入零;●用戶輸入的數(shù)字超出了int類型的最大范圍。所以,編寫了三個(gè)catch塊。如果出現(xiàn)上述異常中的任何一個(gè),程序都會(huì)終止,并向用戶顯示相關(guān)的異常信息和出處,說明程序?yàn)楹侮P(guān)閉。
在finally塊中,檢查去quotient是否為0。如果得到的結(jié)果不為0,就將其顯示給用戶。程序可能的運(yùn)行結(jié)果如圖4-11所示。最后請(qǐng)注意,如果finally語句塊中的代碼拋出異常,程序則會(huì)立即跳出finally語句塊,并將異常拋給調(diào)用者。因此,必須仔細(xì)檢查finally代碼塊中的代碼,以保證finally代碼在執(zhí)行時(shí)不發(fā)生任何錯(cuò)誤;如果不能保證沒有錯(cuò)誤發(fā)生,則應(yīng)該在finally語句塊中使用嵌套的try…catch的結(jié)構(gòu)。圖4-11例4-7各種可能的運(yùn)行結(jié)果4.3屬性屬性是類的成員,它們提供了通過訪問器讀、寫或計(jì)算私有字段值的靈活機(jī)制。如同本書前面一些示例所示,類的創(chuàng)建者往往想要?jiǎng)?chuàng)建一個(gè)對(duì)象使用者能使用的字段,而保持對(duì)該字段的操作控制。例如,可能想要限制該字段的賦值范圍。請(qǐng)先研究下面的示例:
usingSystem;classStudent{ publicstringsName; publicstringsAge;}classTest{ publicstaticvoidMain(){ Studentstd1=newStudent(); Console.WriteLine("輸入學(xué)生姓名:"); std1.sName=Console.ReadLine(); Console.WriteLine("輸入學(xué)生年齡:"); std1.sAge=Console.ReadLine(); Console.WriteLine("姓名:{0},年齡:{1}",std1.sName,std1.sAge); }}
雖然上述會(huì)產(chǎn)生正常的結(jié)果,但將類的數(shù)據(jù)成員公開不是好的編程方法。因?yàn)槲覀冊(cè)谑褂糜脩糨斎胧纠臄?shù)值前沒有進(jìn)行驗(yàn)證,所以用戶可以輸入任何值(上例中年齡值不合常理)。如果要實(shí)現(xiàn)驗(yàn)證,可以在Student類中編寫一個(gè)方法,該方法獲取字段的值并進(jìn)行判斷。
下面的代碼演示了這種方式:
classStudent{ publicstringsName; privatestringsAge; publicvoidSet(stringvalue) { intage=Convert.ToInt16(value); if(age<1||age>150) { sAge="0"; } else { sAge=value; } } publicStringGet() { returnsAge; }}
設(shè)置sAge字段的值時(shí),需要調(diào)用Set方法,獲取該字段的值則需要調(diào)用Get方法。顯然,為每一個(gè)字段編寫兩個(gè)方法是非常繁瑣的,很難實(shí)現(xiàn)類的封裝性和安全性。C#的屬性提供了一種更好、更直接的方式,它的基本形式為:
typename{get{//獲得域變量的值
}set{//設(shè)置域變量的值
}}其中,type指定屬性的類型,name指定屬性的名稱。get訪問器和set訪問器用于讀取和設(shè)置字段的值。
【例4-8】
下面的代碼演示了屬性的創(chuàng)建和使用。
classStudent{ publicstringsName; privatestringsAge; publicstringAge //Age屬性封裝了sAge { get{returnsAge;} set{sAge=value;} }}classTest{ publicstaticvoidMain() { Studentstd1=newStudent(); std1.sName="Mike"; std1.Age="12"; //設(shè)置屬性
Console.WriteLine("姓名:{0},年齡:{1}",std1.sName,std1.Age); //讀取屬性
}}
上面的例子對(duì)私有字段sAge進(jìn)行了屬性封裝,在測(cè)試類中,均通過屬性Age進(jìn)行讀取和設(shè)置。程序的運(yùn)行結(jié)果如圖4-12所示。圖4-12例4-8運(yùn)行結(jié)果
分析上面的程序,我們發(fā)現(xiàn),屬性由兩部分組成:get訪問器和set訪問器。這里的訪問器不是方法,而是用大括號(hào)括起來的代碼段。屬性是對(duì)字段的封裝,get訪問器用于獲取字段的值,采用的是return語句;而set訪問器則是將外部的值賦給該字段,其中value是關(guān)鍵字,表示外部值。這樣,對(duì)屬性的讀/寫操作實(shí)際上是對(duì)字段的操作。圖4-13詳細(xì)地說明了工作過程。圖4-13屬性的工作過程4.3.1屬性的類型屬性可以分為以下三種不同的類型。
1.讀/寫屬性讀/寫屬性是既有set訪問器又有g(shù)et訪問器的屬性。上面例子中的屬性均屬于這一類型。這種類型的屬性同時(shí)提供對(duì)數(shù)據(jù)成員的讀訪問和寫訪問的能力。
2.只讀屬性只有g(shù)et訪問器的屬性稱為只讀屬性。
【例4-9】
下面的代碼演示了只讀屬性的創(chuàng)建和使用。
publicclassStudent{ privateintaverageScore=70; publicintAverageScore //只讀屬性
{ get{returnaverageScore;} }}classTest{ staticvoidMain() { Students1=newStudent(); Console.WriteLine("平均分為{0}",s1.AverageScore); //讀取屬性的值
//s1.AverageScore=88;此行將產(chǎn)生編譯錯(cuò)誤,試圖對(duì)只讀屬性賦值
}}
在該例中,我們創(chuàng)建了類Student的只讀屬性AverageScore。在Main函數(shù)中,實(shí)例化該對(duì)象后讀取該屬性值并顯示出來。在對(duì)該屬性賦值時(shí),系統(tǒng)將產(chǎn)生編譯錯(cuò)誤,原因是AverageScore屬性缺少set訪問器,無法對(duì)其進(jìn)行寫操作。既然只讀屬性不允許修改,有沒有其他的方法可以操作字段的值呢?我們可以使用方法來完成這個(gè)任務(wù)。請(qǐng)看以下代碼段:
…publicclassStudent{ privateintaverageScore=70;publicintAverageScore //只讀屬性,不能改變字段的值
{ get{returnaverageScore;} } publicvoidChangeScore() //改變字段值的方法
{ averageScore+=10; }}3.只寫屬性只寫屬性與只讀屬性正好相反,這種類型的屬性只包含set訪問器。因?yàn)檫@種屬性沒有g(shù)et訪問器,所以只能對(duì)其賦值,而不能檢索它的值。如果試圖檢索它的值,編譯器就會(huì)出現(xiàn)錯(cuò)誤。一般認(rèn)為只寫屬性是沒有意義的,只起到一個(gè)對(duì)稱的作用。所以,我們不再進(jìn)一步討論只寫屬性。
注意:在?.NET?2.0版本中,get和set訪問器的訪問修飾符可以不同,通常是在保持get訪問器可公開訪問的情況下,限制set訪問器的可訪問性。4.3.2屬性約束屬性有一些重要的約束。第一,因?yàn)閷傩圆欢x存儲(chǔ)位置,所以它不能作為ref參數(shù)或out參數(shù)傳遞給方法。第二,用戶不能重載屬性??梢杂袃蓚€(gè)不同的、但訪問相同變量的屬性,但這將是異常的。最后,調(diào)用get訪問器時(shí),屬性最好不要改變默認(rèn)變量的狀態(tài),但編譯器不強(qiáng)制使用此規(guī)則。get操作應(yīng)該是非侵入性的。由此我們引出屬性與字段的比較:
(1)屬性是邏輯字段。屬性通過get方法和set方法實(shí)現(xiàn)對(duì)字段的封裝。在get方法和set方法中,甚至可以改變?cè)甲兞康闹怠?2)屬性是字段的擴(kuò)展。屬性必須和所封裝的字段保持相同的返回值。與字段類似,屬性也可以附加任何訪問修飾符,屬性也可以是static的。
(3)與字段不同,屬性不直接對(duì)應(yīng)于存儲(chǔ)位置。屬性與方法不同,既不使用圓括號(hào),也不必指定void,而對(duì)實(shí)現(xiàn)相似功能的方法而言,則是必須的。4.4索引器索引器是C#?特有的一種訪問技術(shù)。假設(shè)在類中定義了一個(gè)數(shù)組,在訪問數(shù)組元素時(shí),傳統(tǒng)的做法是在對(duì)象名后指定數(shù)組名,同時(shí)指定要訪問元素的索引值,才能進(jìn)行賦值等操作。用C#?的索引器技術(shù),可以為該數(shù)組創(chuàng)建索引器,然后通過從對(duì)象直接指定索引的方式來訪問數(shù)組里的元素。這兩種方式從功能上說是等價(jià)的。請(qǐng)看下面的代碼段:
…stringname1=listBox1.Items[0].ToString() //傳統(tǒng)方法
stringname2=listBox1[0].ToString() //索引器方法
…
在上面的代碼中,listBox1對(duì)象有一個(gè)數(shù)組成員Items,要獲取列表框listBox1的第一個(gè)數(shù)據(jù)項(xiàng)的值,傳統(tǒng)的方法如第一行所示。由于使用了索引技術(shù),我們也可以采用第二行的方法直接通過對(duì)象索引的方式獲取。4.4.1索引器的創(chuàng)建一維索引器的語法如下:
AccessModifierReturnTypethis[DataTypeIndex]{ get{//…} set{//…}}
其中,AccessModifier是訪問修飾符。ReturnType是索引的基類型,因此索引訪問的每個(gè)元素應(yīng)該是ReturnType類型。參數(shù)Index接收被訪問元素的下標(biāo)。理論上,該參數(shù)不必是int類型,但是,因?yàn)樗饕ǔS脕硖峁?shù)組索引,所以常為整數(shù)類型。
與屬性一樣,索引也定義了兩個(gè)訪問器:get和set。使用索引時(shí),自動(dòng)調(diào)用訪問器,且這兩個(gè)訪問器都接收Index作為參數(shù)。
【例4-10】
下面的代碼演示了索引器的創(chuàng)建和使用。
classSampleCollection<T>{privateT[]arr=newT[100];//泛型數(shù)組
publicTthis[inti] //為泛型數(shù)組創(chuàng)建泛型索引器
{get{returnarr[i];}set{arr[i]=value;}}}//調(diào)用類classProgram{staticvoidMain(string[]args){SampleCollection<string>stringCollection=newSampleCollection<string>();stringCollection[0]="Hello,World"; //使用索引器對(duì)其數(shù)組成員賦值
System.Console.WriteLine(stringCollection[0]); //使用索引器將數(shù)組成員打印出來
}}
上面的代碼首先定義了一個(gè)泛型類SampleCollection,并為其提供了簡(jiǎn)單的get和set訪問器方法(作為分配和檢索值的方法)。注意索引器必須命名為this,因?yàn)橐L問的索引器必須是它們所屬的對(duì)象。Program類為存儲(chǔ)字符串創(chuàng)建了此類的一個(gè)實(shí)例,隨后一行使用索引器來設(shè)置類中數(shù)組成員arr的第一個(gè)值,接著輸出該值。程序最終將顯示Hello,World。注意:泛型是?.NET?2.0的一個(gè)新功能。泛型將類型參數(shù)引入?.NETFramework,使用泛型類型參數(shù),可以編寫客戶端代碼能夠使用的單個(gè)類,而不會(huì)導(dǎo)致運(yùn)行時(shí)強(qiáng)制轉(zhuǎn)換或裝箱帶來的風(fēng)險(xiǎn)。
聲明索引器的步驟如下:
(1)指定索引器的訪問修飾符。
(2)說明索引器的返回類型(get訪問器的返回類型)。
(3)指定this關(guān)鍵字。
(4)指定索引的數(shù)據(jù)類型(與數(shù)組不同,索引器的索引不一定為整型)。
(5)指定聲明索引的變量名。
(6)最后完成get和set訪問器的內(nèi)容,如同定義屬性一樣。在接下來的例子中,我們來看看如何使用字符串作為索引器的索引。
【例4-11】
下面的代碼演示了具有字符串索引的索引器的創(chuàng)建和使用。
classDayCollection{string[]days={"Sun","Mon","Tues","Wed","Thurs","Fri","Sat"};privateintGetDay(stringtestDay){inti=0;foreach(stringdayindays){if(day==testDay){returni;}i++;}return-1;}publicintthis[stringday]{get{return(GetDay(day));}}}classProgram{staticvoidMain(string[]args){DayCollectionweek=newDayCollection();System.Console.WriteLine(week["Fri"]);System.Console.WriteLine(week["Made-upDay"]);}}
在此例中,聲明了存儲(chǔ)星期幾的類,同時(shí)聲明了一個(gè)get訪問器,它接受字符串(無名稱),并返回相應(yīng)的整數(shù)。例如,星期日將返回0,星期一將返回1,等等。注意,這是一個(gè)只讀索引,試圖對(duì)該索引賦值都講產(chǎn)生錯(cuò)誤。最后的輸出是:
5-14.4.2多參數(shù)索引器在索引器中可以指定一個(gè)以上的索引器參數(shù)。多個(gè)參數(shù)意味著要以類似于訪問多維數(shù)組的方式訪問索引器。
【例4-12】
下面的代碼演示了多參數(shù)索引器的創(chuàng)建和使用。
publicclassArray2D { int[,]a; introws,cols; publicArray2D(intr,intc) //構(gòu)造函數(shù)
{ rows=r; cols=c; a=newint[rows,cols]; }publicintthis[intindex1,intindex2]//二參數(shù)索引器
{ get{returna[index1,index2];//返回二維數(shù)組的一個(gè)元素
set{a[index1,index2]=value;}//對(duì)二維數(shù)組的一個(gè)元素賦值
} } publicclassTest { staticvoidMain() { Array2Darr=newArray2D(2,3); for(inti=0;i<2;i++){ for(intj=0;j<3;j++) { arr[i,j]=i+j; //設(shè)置索引器Console.Write(arr[i,j]+""); //顯示索引器
} Console.WriteLine(); } } }
在上述示例中,定義的二維索引為該類中的二維數(shù)組a進(jìn)行包裝。在讀取該數(shù)組的值時(shí),完全不用調(diào)用數(shù)組a(同時(shí)a也是私有的,無法調(diào)用),而直接采用類名調(diào)用即可。該示例的輸出結(jié)果為:
0121234.5委托什么是委托?在現(xiàn)實(shí)生活中,委托就是讓別人去代替自己辦事。比如你有一份資料要發(fā)給客戶,而自己不便親自去做。這時(shí)可以派助手小王去給用戶發(fā)傳真,也可以讓小王發(fā)電子郵件。處理的方法有很多種,小王只需要獲得你的授權(quán)拿到資料就可以為你辦事了。在C#中,可以把委托看做一個(gè)方法指針,該指針在不同時(shí)刻可以指向不同的方法,并且可以通過該委托執(zhí)行這些方法。委托如何做到這點(diǎn)呢?在面向?qū)ο蟮某绦蛟O(shè)計(jì)中,委托是能夠引用方法的對(duì)象。因此,創(chuàng)建委托時(shí),創(chuàng)建的是能夠存儲(chǔ)方法引用的對(duì)象。怎樣理解方法的引用?我們知道,引用的本質(zhì)是內(nèi)存地址,因此,對(duì)象引用就是對(duì)象的地址。方法雖然不是對(duì)象,它同樣在內(nèi)存中有一個(gè)物理位置,而且其入口點(diǎn)就是調(diào)用方法時(shí)調(diào)用的地址。將此地址傳給委托,一但委托引用一個(gè)方法,就能夠通過該委托來調(diào)用該方法。例如,假設(shè)有兩個(gè)方法MulFun和AddFun。MulFun接受兩個(gè)整型參數(shù),返回它們的乘積。AddFun接受兩個(gè)整型參數(shù),返回它們的和。既然兩個(gè)方法的參數(shù)個(gè)數(shù)、類型和返回值類型都一樣,就可以創(chuàng)建一個(gè)委托CalFunDelegate,使其在某個(gè)時(shí)刻指向MulFun,在某個(gè)時(shí)刻指向AddFun。如果該委托在指向MulFun時(shí)調(diào)用,則將對(duì)委托中傳遞的參數(shù)做乘法計(jì)算;如果該委托在指向AddFun時(shí)調(diào)用,則將對(duì)委托中傳遞的參數(shù)做加法計(jì)算。4.5.1定義委托定義委托的語法如下:
AccessModifierdelegateReturnTypeDelegateName(parameter-list);其中,AccessModifier是訪問修飾符,Delegate是委托關(guān)鍵字,ReturnType和parameter-list是委托所引用方法的返回值類型和參數(shù)列表。這里應(yīng)注意,定義委托和定義方法非常類似,只是沒有定義委托的實(shí)現(xiàn),因此委托的語法以分號(hào)結(jié)束。
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 公司公益活動(dòng)策劃書
- 第五章-廣播電視新聞性節(jié)目
- 2024至2030年中國(guó)絨玩具數(shù)據(jù)監(jiān)測(cè)研究報(bào)告
- 2024至2030年中國(guó)循環(huán)水冷卻機(jī)數(shù)據(jù)監(jiān)測(cè)研究報(bào)告
- 2024至2030年中國(guó)4通道應(yīng)變調(diào)理卡數(shù)據(jù)監(jiān)測(cè)研究報(bào)告
- 2024年中國(guó)酶生化試劑市場(chǎng)調(diào)查研究報(bào)告
- 2024年中國(guó)聯(lián)網(wǎng)電話報(bào)警系統(tǒng)市場(chǎng)調(diào)查研究報(bào)告
- 2024年中國(guó)改性瀝青高速剪切儀市場(chǎng)調(diào)查研究報(bào)告
- 2024年中國(guó)萬能夾板機(jī)市場(chǎng)調(diào)查研究報(bào)告
- 2024年08月天津友利銀行天津分行一般行員招考筆試歷年參考題庫附帶答案詳解
- 軍工合作合同范例
- 【7地XJ期末】安徽省宣城市寧國(guó)市2023-2024學(xué)年七年級(jí)上學(xué)期期末考試地理試題(含解析)
- 2025年中國(guó)稀土集團(tuán)總部部分崗位社會(huì)公開招聘管理單位筆試遴選500模擬題附帶答案詳解
- 超市柜臺(tái)長(zhǎng)期出租合同范例
- 設(shè)備操作、保養(yǎng)和維修規(guī)定(4篇)
- 廣東省廣州市2025屆高三上學(xué)期12月調(diào)研測(cè)試語文試題(含答案)
- 【8物(科)期末】合肥市第四十五中學(xué)2023-2024學(xué)年八年級(jí)上學(xué)期期末物理試題
- 統(tǒng)編版2024-2025學(xué)年三年級(jí)語文上冊(cè)期末學(xué)業(yè)質(zhì)量監(jiān)測(cè)試卷(含答案)
- 從0 開始運(yùn)營(yíng)抖?音號(hào)sop 文檔
- 2025年度日歷臺(tái)歷黃歷模板
- 醫(yī)療行業(yè)十四五規(guī)劃
評(píng)論
0/150
提交評(píng)論