1. 程式人生 > >C#復習筆記(2)--C#1所搭建的核心基礎

C#復習筆記(2)--C#1所搭建的核心基礎

ces 特性 函數 不同的 是不是 multicast 屬性 異常 編譯

通過對C#1所搭建的核心基礎的深入了解,可以知道之後的C#版本在C#1的基礎上做了很多擴展,而這些擴展都是基於C#搭建的核心基礎而來的。

委托

一、編寫委托的過程

委托經常和C語言的“函數指針”掛鉤。委托是方法參數化、函數式語言一個重要的表達方式。C#1中編寫一個委托要經過四部:

1、聲明委托類型

delegate void StringProcessor(string param1);

這個委托指定了一種無返回值,有一個string類型的參數的方法。

這個委托繼承自System.MulticastDelegate,後者又派生自System.Delegate.

委托本身是引用類型,所以聲明委托的時候不能在方法中聲明。可以作為內部類,也可以聲明到namaspace下面。

2.、必須有一個方法包含了要執行的代碼,這個方法要和委托聲明的簽名一致,包括參數和返回值

C#一中要為委托找到一個方法的話必須要和委托的簽名完全一直,在C#2中允許委托的斜邊和逆變。必須下面的委托:

delegate object SayHello(string word);  

這個委托可以被下面這個方法實例化:

 static string Sayhello(object word)
        {
            Console.WriteLine(word);
            return word.ToString();
        }

3、創建委托本身


可以采用new操作符來創建一個委托的實例:

 SayHello say = new SayHello(Sayhello);

C#2中支持委托-方法組的轉換,所以,可以直接使用

SayHello say = Sayhello;

4、調用委托

一切準備就緒後就可以用invoke方法來完成調用。C#還可以更簡單的完成這個操作,使用

SayHello say = Sayhello;
            say("hello,you");

就可以完成,不過在背後編譯器幫你完成了一些工作:

技術分享圖片

二、合並和刪除委托


委托和string的特征有一些相似:都是不易變的。這體現在委托的合並和刪除上面:

委托內部有一個操作列表(invocation list):System.Delegate的靜態方法Combine和Remove負責創建新的委托實例。其中,Combine將兩個委托的操作列表鏈接到一起,Remove負責將一個委托實例的一個操作刪除。它們都不改變原有的類型(所以說和string很像)他們都是返回一個Delegate。

很少在代碼中直接調用Delegate.Combine,Delegate.Remove而是用+=和-=操作符。這同樣是編譯器的功勞:

技術分享圖片

如果調用列表中拋出異常,那麽拋出異常的那個方法導致調用列表不在執行。

如果有返回值,那麽調用列表會返回最後調用的那個方法的值。所以一般不會在調用列表中執行有返回值的方法。

但是如果有必要,可以通過調用invocationlist來逐個執行方法並獲取返回值。

三、事件

首先來看事件的聲明:

public event SayHello SayHelloEvent;

事件是對委托的封裝,有點兒類似於屬性和後備字段的關系,上面這句聲明編譯器會做如下工作:

1、在相同的作用域中聲明一個SayHello委托類型的私有字段

2、聲明一個類似於屬性的塊結構,這個塊結構包含一個類似於屬性的取值方法和賦值方法,由編譯器進行命名,前綴分別是"add"和"remove"。

3、在外部,會通過+=和-=來操作事件,聲明事件時的訪問修飾符會限制這一操作。就是說如果聲明為一個private的事件的話外部是不能操作事件的。

4、在內部,+=操作符會調用“add”前綴的方法,“add”前綴的方法會調用Delegate.Combine來合並操作,同理,-=操作符會調用“remove”前綴的方法,該方法會調用Delegate.Remove來刪除操作。

5、從上面可以看出,事件就是一對兒“add"和”remove“方法。他封裝了委托,從而避免調用方直接操作委托,而是通過事件來間接的操作委托,在類的內部可以看到委托,在類外部可以看見事件。

總結一下,事件不是委托實例,只是成對兒出現的add/remove方法。類似與屬性的get/set方法。

類型系統的特征

在C#4之前,C#的類型系統是靜態的、顯式的和安全的。

一、靜態類型和動態類型

C#是靜態類型的:每個變量或表達式的類型在編譯時都是已知的。只有類型已知的操作才是被允許的。靜態這個詞用來表示使用不變的類型數據來分析哪些操作可用。

與靜態類型想對應的是動態類型,動態類型的實質是變量中含有值,但那些值不限於特定的類型。所以編譯器不能執行相同形式的檢查。

二、顯示類型和隱式類型

這個討論只有在靜態語言的環境中才是成立的。對於顯式類型來說,每個變量都在聲明時指定類型。隱式類型則允許編譯器變量的用途來確定變量的類型。無論是顯式還是隱式,表達式的類型都會在編譯時就確定。

三、類型安全與類型不安全

C#是類型安全的,C#支持一些類型安全的轉換:繼承上的、數值上的,如果使用強制類型轉換,編譯器會檢測轉換的結果,也會拋出異常來組織程序的繼續運行,C#還支持有限的協變和逆變(C#4)。但是和真正的協變和逆變還有很長一段距離。

可以通過顯式實現接口來處理協變和逆變上的一些限制。

總結一下:

C# 1是靜態類型的—— 編譯器知道你能使用哪些成員;

C# 1是顯式的—— 必須告訴編譯器變量具有什麽類型;

C# 1是安全的——除非存在真實的轉換關系, 否則不能將一種類型當做另一種類型;

靜態類型仍然不允許一個集合成為強類型的“ 字符串列表” 或者“ 整數列表”, 除非針對不同的元素使用大量的重復代碼;

方法覆蓋和接口實現不允許協變性/ 逆變性。

值類型和引用類型

一、值類型和引用類型的基礎知識

對於引用類型的表達式(如一個變量),它的值是一個引用,而非對象。
引用就像URL——是允許你訪問真實信息的一小片數據。

對於值類型的表達式,它的值是實際的數據。

有時,值類型比引用類型更有效, 有時恰好相反。
引用類型的對象總是在堆 上, 值類型的值既可能在棧 上, 也可能在堆上,具體取決於上下文。
引用類型作為方法參數使用時, 參數默認是以“值傳遞”方式來傳遞的—— 但值本身是一個引用。

值類型和引用類型的本質區別在於復制的方式不同:值類型復制值本身,引用類型復制的是引用。

兩種類型的另一個差異在於, 值類型不可以派生出其他類型。 這將導致的一個結果就是,值不需要額外的信息來描述值實際是什麽類型。 把它同引用類型比較, 對於引用類型來說, 每個對象的開頭都包含一個數據塊, 它標識了對象的實際類型, 同時還提供了其他 一些信息。 永遠都不能改變對象的類型——執行簡單的強制類型轉換時, 運行時會獲取一個引用, 檢查它引用的對象是不是目標類型的一個有效對象。如果有效, 就返回原始引用; 否則拋出異常。 引用本身並不知道對象的類型—— 所以同一個引用“ 值” 可用於( 引用) 不同類型的多個變量。 例如 下面 的 代碼:

 Stream stream=new MemoryStream();
            MemoryStream memory = (MemoryStream) stream;
//第1行創建一個新的MemoryStream對象, 並將stream變量的值設為對那個新對象的引用。 第2行檢查stream的值引用的是不是一個MemoryStream( 或派生類型)對象,並將MemoryStream的值設為相同的值。

值類型變量本身存儲的是值,引用類型變量本身存儲的是引用,這個引用裏面包含一個指向真正對象的地址。變量的值在它聲明時的位置存儲。局部變量的值總是存儲在棧( stack)中(這個結論只有 在C#1中完全成立。以後會講到,在更高版本C#中,在特定情況下,局部變量最終可能存儲在堆中。)實例變量的值總是存儲在實例本身存儲的地方。 引用類型實例(對象)總是存儲在堆( heap)中, 靜態變量也是。

按值傳遞和按引用傳遞的區別是按值傳遞的是副本,按引用傳遞的是別名。

裝箱的背景在於值類型和引用類型的值不同:值類型的值就是值本身,而引用類型的值只是一個引用。

值類型的值會在需要引用類型的行為時被裝箱; 拆箱則是相反的過程。

構建於C#1之上的新特性

一、與委托有關的新特性

C#1中的委托-->C#2的泛型、方法組轉換、匿名方法、委托協變性和逆變性-->C#3的lambda表達式、內建的Func、Action、泛型的接口和委托的協變性和逆變性

二、與類型系統有關的特性

C#2-->泛型、委托的協變性和逆變性-->C#3匿名類型、隱式類型、擴展方法-->C#4受限的泛型協變性和逆變性、動態類型

三、與值類型有關的特性

C#2-->泛型、Nullable<t>可空值類型

C#復習筆記(2)--C#1所搭建的核心基礎