通過對c#1所搭建的核心基礎的深入了解,可以知道之後的c#版本在c#1的基礎上做了很多擴充套件,而這些擴充套件都是基於c#搭建的核心基礎而來的。
委託經常和c語言的「函式指標」掛鉤。委託是方法引數化、函式式語言乙個重要的表達方式。c#1中編寫乙個委託要經過四部:
delegate void stringprocessor(string param1);
這個委託指定了一種無返回值,有乙個string型別的引數的方法。
這個委託繼承自system.multicastdelegate,後者又派生自system.delegate.
委託本身是引用型別,所以宣告委託的時候不能在方法中宣告。可以作為內部類,也可以宣告到namaspace下面。
c#一中要為委託找到乙個方法的話必須要和委託的簽名完全一直,在c#2中允許委託的斜邊和逆變。必須下面的委託:
delegate object sayhello(string word);這個委託可以被下面這個方法例項化:
static string sayhello(objectword)可以採用new操作符來建立乙個委託的例項:
sayhello say = new sayhello(sayhello);c#2中支援委託-方法組的轉換,所以,可以直接使用
sayhello say = sayhello;一切準備就緒後就可以用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#2的泛型、方法組轉換、匿名方法、委託協變性和逆變性-->c#3的lambda表示式、內建的func、action、泛型的介面和委託的協變性和逆變性
c#2-->泛型、委託的協變性和逆變性-->c#3匿名型別、隱式型別、擴充套件方法-->c#4受限的泛型協變性和逆變性、動態型別
c#2-->泛型、nullable可空值型別
c 複習筆記1
1.using空間的使用 在using空間中的類不能顯示的新增private,protected等,c 方法預設訪問級別 private,c 類預設訪問級別 internal 2.using system using n1 等這樣的語句必須放在整個文件的最前面。3.命名空間就像乙個倉庫,using就...
C指標 複習筆記2
1.void 可以定義變數 void p p的型別為void 而void 指標型別,32為平台4位元組 p叫萬能指標 p可以儲存 任意一級指標 char ch p ch 型別 char int num p 型別 int float f p f 型別 flaot p操作,需對p進行強制型別轉換 voi...
我的c語言複習筆記1 說說for迴圈
注1 這篇文章主要參考了 1 我們先來看最簡單的for迴圈 int i i是變數 for i 0 i 5 i for 表示式 1 表示式 2 表示式 3 語句 當for迴圈內只有一條語句的時候,可以省略 它的輸出結果是01234 它的執行過程如下 1 求表示式1 2 求表示式2 若其值為真,則執行f...