C#高階程式設計 讀書筆記

DavidQian諦聽發表於2018-11-07


.NET Framework 是微軟開發的軟體環境,而C#是專門用於.NET Framework 的程式語言。
.NET 的公共語言執行庫CLR可以將原始碼編譯成MS的中間語言程式碼IL,類似於Java位元組碼:平臺無關性(並不完全)、提高效能、語言的互操作性。

.NET執行庫對於垃圾回收採用的是垃圾回收器,所有動態請求的記憶體都分配到堆上,每隔一段時間,當.NET檢測到需要清理記憶體時就呼叫垃圾回收器。垃圾回收的重要性質是不確定性,也就是說不能保證什麼時候會呼叫垃圾回收器,這個時間由CLR決定。

.NET擁有異常機制來處理錯誤,也就是try{}catch{}finall{}程式碼塊。

.NET支援特性的使用。

C# 常量(更容易讀懂、修改,且更好避免出錯)
1.常量必須在宣告時初始化且不能用變數來初始化,初始化後不能改寫
2.常量用const修飾,不允許再常量宣告再包含修飾符static

C# 基本型別
1.值型別:直接儲存值,儲存再棧上
2.引用型別:儲存的是對值得引用地址,通過該地址可以找到值,儲存在堆上

Vector x,y;
x = new Vecot();
x.val = 30;
y = x;
Console.WriteLine(y.val);
y.val = 50;
Consolo.WriteLine(x.val);

在上面的程式中,Vector是引用型別,x指向了新建物件(new),然後x的val變成了30,y指向了x,因此第一個Console顯示的是 30,然後y的val變成了50,所以第二個Console顯示的是50。在這裡記憶體只有一個物件就是new那個,而x指向了該物件,在y = x,y也指向了這個物件,因此x和y指向的是同一個物件,對x和y的操作都會直接影響那個物件。這就是引用型別的特點。而值型別的情況是這樣的:宣告瞭一個bool型別的變數,並且賦值false,將這個變數賦值給另一個bool變數,這樣會使得記憶體中存在兩個bool值,如果其中的一個值修改為true,那記憶體中有一個是true另一個是false。

在C#中short,int,long型別的長度並不受平臺限制,總是16,32,64位帶符號整數。float是32位單精度浮點數,double是64位雙精度浮點數。decimal是128位高精度十進位制數,如果要指定數字為decimal型別的需要在數字後面加上M。C#的char是16位的Unicode字元。C#支援兩種預定義的引用型別object(基類)和string(Unicode字串)

string型別
string是一種引用型別,他的物件仍然分配在堆上,但是與其他引用型別不完全一樣
1.當把一個string變數賦值給另一個string變數的時候,記憶體中仍然是一個物件兩個引用,但是修改其中一個變數的字串的時候,會在堆上再分配新的string物件,這時記憶體中就有兩個物件,各自有一個引用,修改各自變數的值不會互相影響。

if中的表示式必須等於bool值,不像C++那樣可以用整數

C#的switch語句中如果啟用了一個較前的case子句,後面的case子句就不會被啟用,沒有break的case將被編譯器視為fault。例外的情況時,如果case子句為空,那就可以從這個case跳到下一個case。
C#的break語句可以用於跳出迴圈語句,然後執行迴圈語句後面一條語句。
C#的continue語句用於跳出當前迴圈迭代,開始執行下一次迴圈迭代。
C#的return語句用於退出類的方法。
十一
列舉
列舉是使用者定義的整數型別,在宣告時就要指定列舉值

public enum TimeOfDay
{
	Morning = 0,
	Afternoon = 1,
	Evening = 2
}
//呼叫TimeOfDay.Morning就會得到0。

十二
using語句
1.用於引入名稱空間
2.給類和名稱空間指定別名
3.類似try{}catch{}結構
十三
C#的前處理器指令
如 #region和#endregion用於給程式碼塊指定名稱
十四
C#的變數名不能是C#關鍵字且必須以字母或者下劃線開頭
十五
命名約定
1.推薦camel大小寫形式:第一個單詞首字母小寫,後面單詞首字母大寫
2.類中私有成員欄位名使用camel,並在欄位前加一個下劃線
3.引數使用camel
4.類名、方法名第一個單詞首字母大寫,其餘單詞首字母也大寫
十六
結構和類的區別
主要區別是值型別和引用型別的區別
十七
在方法傳參時,一般來說,引用型別通過引用傳遞,值型別通過值傳遞來傳遞引數。值傳遞時,方法獲得的是該值的一個資料副本,在方法中對該資料副本作任何操作不會影響原資料;引用傳遞時,方法獲得是這個引用,通過這個引用修改資料會修改原資料。
十八
非要將值型別資料使用引用傳遞的方式傳給方法,比如在該值所佔記憶體很大的時候,可以使用關鍵字ref,值傳遞變成引用傳遞可以避免複製該值減少記憶體消耗。使用方法是在函式定義的時候,在該引數的型別前面加上關鍵字ref,同時,在呼叫函式時,引數變數的前面也加上關鍵字ref。
另外,C#要求傳遞給任何方法的任何變數要初始化。
十九
out引數
有時候引用傳遞傳進來的引數的初值沒有意義,因為他可能被重寫。
但C#又有引數初始化的要求,這時可以使用out關鍵字,這樣做可以使該引數不用被初始化,並且該引數是通過引用傳遞的。使用方法是在定義函式的時候,在該引數的型別前面加上關鍵字out,同時,在呼叫函式的時候在引數變數前面也加上out。
二十
可選引數
在函式傳參過程中,有時候有些引數不一定存在,因此可以定義為可選引數。可選引數的位置必須在非可選引數後面並且必須提供預設值。
二十一
方法過載
方法過載是在函式設計的時候函式可能有不同幾個版本,實現方法是宣告同名但是引數個數或者型別不同的方法。
二十一
一般情況下,C#不需要自己編寫類的建構函式,因為編譯器在編譯時自己會建立預設建構函式。如果需要帶引數的建構函式這時候就需要編寫建構函式。
二十二
靜態建構函式
靜態建構函式的使用原因是,類的一些靜態欄位需要在第一次使用類之前,從外部資料來源初始化這些靜態欄位。靜態建構函式不允許private和public這樣的訪問修飾符修飾。靜態建構函式與例項建構函式不同,即使是同參,兩個建構函式也可以存在於同一個類定義中,因為靜態建構函式在載入類的時候執行,例項建構函式在建立例項時執行。但是由於靜態建構函式是在載入類的時候執行,因此當存在多個類都有靜態建構函式,就不能確定到底哪個類的靜態建構函式先行執行,因此編碼不應該有靜態建構函式相互依賴的情況。
二十三
只讀欄位readonly
只讀欄位readonly與const一樣修飾常量,常量是值不能改變的變數。但是readonly更加靈活。readonly允許將欄位設定為常量,這個常量來自於某些計算結果。
二十四
匿名型別var
匿名型別只是一個繼承自Object且沒有名稱的類。這個類的定義從初始化器中推斷。
二十五
部分類
多個人員開發的時候,類的定義可能會存在於多個檔案中,這時候使用部分類可以使得在類定義分佈在多個檔案中。使用方法是在class前面加上關鍵字partial
二十六
靜態類
靜態類只能包含靜態屬性和靜態方法,並不能建立靜態類的例項。靜態的意思是在程式載入的時候執行,而且只會執行一次。
需要詳細介紹靜態
二十七
Object類
Object類是.NET的所有類的基類。
二十八
實現繼承和介面繼承
在C#中,實現繼承是一個類派生自一個基類,那子類就擁有基類的成員欄位和函式。介面繼承是類只是繼承了函式的簽名,沒有繼承任何實現程式碼。
二十九
多重繼承
C#不支援多重繼承,但是C#允許類繼承自多個介面。也就是多重介面繼承。在定義類的時候只要把多個介面基類用“ , ”號隔開,就可以實現多重介面繼承。
三十
虛方法
把一個基類的函式宣告為virtual,就可以在子類中重寫該函式。重寫的關鍵字是override。

class MyBaseClass
{
	public virtual string getString()
	{
		retrurn " This is a string ";
	}
}
class MyDerivedClass:MyBaseClass
{
	public override string getString()
	{
		return " This is still a string ";
	}
}

三十一
C#支援子類呼叫方法的基類版本,只需要在方法名前使用base關鍵字再 " . "出來。

class MyBaseClass
{
	public virtual decimal GetPrice()
	{
		return 0.00M;
	}
}
public MyDerivedClass:MyBaseClass
{
	public override decimal GerPrice()
	{
		return base.GetPrice()+0.9M;
	}
}

三十二
抽象類和抽象函式
C#允許把類和函式宣告為abstract,這就宣告瞭抽象類和抽象函式。抽象類不能例項化。抽象函式不能直接實現,必須在非抽象的派生類中重寫。抽象函式本身也是虛擬的,但是已經有abstract關鍵字了不能再加上virtual關鍵字。只要類中宣告瞭抽象函式,那類也必須宣告為抽象的。
三十三
密封類和密封方法
C#允許將類和方法宣告為sealed。對於類,這表示其他類不能繼承該類;對於方法,這表示不能重寫該方法。如果將方法宣告為sealed,則必須在基類上把它宣告為virtual或者abstract。
三十四
有繼承關係的類的建構函式的執行順序
當C#例項化一個類物件時,首先會執行它的建構函式。如果類A繼承於類B,那例項化類A的時候,類A的建構函式會執行,類A繼承於類B,那類A的建構函式會先執行類B的建構函式,類B的建構函式執行的時候,由於類B繼承於System.Object,那類B會先執行System.Object的建構函式。這種建構函式的執行順序總是從基類向下迭代。
三十五
含有基類的類的建構函式呼叫
一般情況下建構函式是這樣定義的

public MyClass()
{

}

實際上建構函式預設按這個樣子編譯

public MyClass()
:base()//這一行代表在呼叫這個建構函式的時候,先呼叫基類的預設建構函式
{

}

按照規則,假如自定義了類的建構函式,那麼編譯器編譯時就不會給類生成預設建構函式
假如有以下程式碼

public abstract class MyBaseClass()
{
	private string name;
	public MyBaseClass(string name)
	{
	}
}
public class MyGenericClass:MyBaseClass
{
	private string name;
	public MyGenericClass(string name)
	{
	}
}

上面程式碼會報錯,因為在子類中,沒有明寫出來,呼叫的就是無引數的基類的建構函式,但是基類定義了有參的 建構函式,沒有無參的建構函式,導致編譯出錯。
子類要呼叫基類的有參的建構函式方法如下:

public class MyGenericClass:MyBaseClass
{
	private string name;
	public MyGenericClass(string name)
	:base(name)
	{
	}
}

三十六
可見性修飾符
public:所有程式碼均可訪問
protected:只有派生的型別能夠訪問
private:只能在所屬型別中訪問
internal:只能在包含它的程式集中訪問
protected internal:只能在包含它的程式集和派生類中訪問
類可以定義為public或者internal,但是不能定義為其它可見性修飾符,因為沒有意義,但可以用來定義類的成員屬性和成員方法。
三十七
介面
定義介面不能提供任何成員的實現方式,介面不能有建構函式,也不能被例項化,不能有欄位,介面必須實現。介面成員的可見性修飾符總是public,且不用明寫出來。
簡單說明屬性和欄位的區別

public class Man()
{
	private int _age;//欄位一般私有化,以下劃線開頭
	public int Age//屬性一般公有化,大寫字母開頭,屬性實際上是一個方法
	{
		get { return _age; }
		set { _age = value; }
	}
}

三十八
ArrayList類和List類
ArrayList類儲存的是物件,Add()要求把一個物件作為引數。如果ArrayList的Add操作的是一個int型別的整數,這就會發生裝箱操作,把一個int型別的值型別的值裝箱成一個引用型別的物件。而在讀取這個引用的值的時候,需要拆箱操作將物件轉換為int型別,這時可以使用型別強制轉換運算子。因為ArrayList在這種情況下要進行裝箱拆箱,因此效能損失較大。
List類將泛型T定義為int就有List,泛型類不使用物件,而是使用時定義型別。對於List,在Add()一個int值時不需要進行裝箱操作,因此在獲取值的時候也不需要拆箱。
ArrayList類可以儲存不同型別的物件,不是型別安全的;List泛型類在確定泛型T的型別時只能儲存型別T的值或者物件,因此是型別安全的。
三十九
當只有一個泛型類的時候,用T作為泛型類的名稱;當有多個泛型類,或者泛型類必須實現介面或者派生自基類,泛型類規定用字母T作為字首,加上描述性名稱。
四十
定義以下非泛型的連結串列類

public classs LinkedListNode
{
	public linkedListNode(object value)
	{
		this.Value = value;
	}
	public object Value { get; private set; }
	public LinkedListNode Next { get; internal set; }
	public LinkedListNode Prev { get; internal set; }
}//class LinkedListNode define END

public class LinkedList:IEnumerable
{
	public LinkedListNode First { get; private set; }
	public LinkedListNode Last { get; private set; }
	public LinkedListNode AddLast(object node)
	{
		var newNode = new LinkedListNode(node);
		if(First == null)
		{
			First = newNode;
			Last = First;
		} 
		else
		{
			LinkedListNode previous = Last;
			Last.Next = newNode;
			Last = newNode;
			Last.Prev = previous ; 
		}
		return newNode;
	}
	public IEnumerator GetEnumerator()
	{
		LinkedListNode current = First;
		while(current != null)
		{
			yield return current.Value;
			current = curren.Next;
		}
	}
}//class LinkedList define END

現在照著上面的程式碼,寫一個連結串列類的泛型版本

public class LinkedListNode<T>
{
	public LinkedListNode(T value)
	{
		this.Value = value;
	}
	public T Value{ get; private set; }
	public LinkedListNode<T> Next { get; internal set; }
	public LinkedListNode<T> Prev { get; internal set; }
}//class LinkedListNode<T> define END

public class LinkedList<T>:IEnumerable<T>
{
	public LinkedListNode<T> First { get; private set; }
	public LinkedListNode<T> Last { get;private set; }
	public LinkedListNode<T> AddLast(T node)
	{
		var newNode = new LinkedListNode<T>(node);
		if(First == null)
		{	
			First = newNode;
			Last = First;
		}
		else
		{
			LinkedListNode<T> previous = Last;
			Last.Next = newNode;
			Last = newNode;
			Last.Prev = previous;
		}
		return newNode;
	}
	public IEnumerator<T> GetEnumerator()
	{	
		LinkedListNode<T> current = First;
		while(current != null)
		{
			yield return current.Value;
			current = current.Next;
		}
	}
	IEnumerator IEnumerable.GetEnumerator()
	{
		return GetEnumerator();
	}
}//class LinkedList<T> define END

四十一
泛型類初始化的時候為了避免值型別還是引用型別的問題,應該使用default關鍵字,default關鍵字可以把值型別初始化為0,引用型別初始化為null。例如

public T GetDocument()
{
	T doc = default(T);
	lock(this)
	{
		doc = docDocument.Dequeue();
	}
	return doc;
}

四十二
泛型約束

public interface TDocument
{
	string Title{ get; set; }
	string Content { get; set; }
}
public class Document:IDocument
{
	public Document()
	{
	}
	public Document(string title,string content)
	{
		this.Title = title;
		this.Content = content;
	}
	public string Title { get; set; }
	public string Content { get; set; }
}

suppose codes in DocumentMananger.cs follows that

public void DisplayAllDocument()
{
	foreach(T doc in docmentQueue)
	{
		Console.WriteLine(((IDocument)doc).Title);
	}
}

由於T是泛型,並不確定是哪個型別,更不能確定這個型別是否實現了IDocument介面,因此假如正好它沒有實現,就會導致一個執行時異常,說這個型別強制轉換出錯。為此最好在定義DocumentMananger時要求泛型類必須實現IDocument介面,這裡用TDocument替代T。
codes in DocumentMananger.cs should like this

public class DocumentManager<TDocument>
	where TDocument:IDocument//這個約束說明TDocument要實現IDocument介面
{

}

泛型中有以下約束,
where T:struct ,結構約束,型別T必須是值型別
where T:class ,類約束,型別T必須是引用型別
where T:IFoo ,介面約束,型別T必須實現介面IFoo
where T:Foo ,基類約束,型別T必須派生自基類Foo
where T:new() ,建構函式約束,型別T必須有一個自定義的預設建構函式
where T1:T2 ,裸型別約束,(泛型)型別T1必須派生自泛型型別T2
指定多個約束時用逗號隔開約束。
四十三
C#裡面子類物件是可以賦值給基類引用的,因為子類物件符合基類的任何特點。
四十四
泛型方法

//define function
public void Swap<T>(ref T x,ref T y)
{
	T temp;
	temp = x;
	x = y;
	y = temp;
}
//use function
Swap<int>(ref 4,ref 5);
Swap(ref 4, ref5);//泛型方法可以像非泛型方法那樣呼叫

四十五
泛型方法可以過載,這就關係到呼叫過載方法的引數匹配問題。呼叫過載方法的引數匹配的時機是在編譯期間,編譯器會選擇最佳引數型別匹配。

public class MethodOverloads
{
	public void Foo(int x)
	{
		Console.WriteLine(" Foo(int x) ");
	}
	public void Foo<T>(T obj)
	{
		Conosle.WriteLine(" Foo<T>(T obj)  and type: {0} ",obj.GetType().Name);
	}
	public void Bar<T>(T obj)
	{
		Foo(obj);
	}
}
//codes in Main Function
var test = new MethodOverloads();
test.Foo(11);
test.Foo("ab");
test.Bar(44);
//console writes
Foo(int x)
Foo<T>(T obj) and type: String
Foo<T>(T obj) and type:  Int32

第一個輸出毫無懸念;第二個輸出基於最佳匹配,沒有string型別引數的Foo函式,自然匹配泛型引數的Foo函式;第三個輸出,過載函式的引數匹配是在編譯期間的,所以泛型引數的Bar函式會呼叫泛型引數的Foo函式(最佳匹配,大家引數型別都是泛型),而函式傳參的時機是在執行期間,所以傳遞的引數型別是Int型別。
四十六
陣列是引用型別,在用new初始化的時候可以給出陣列大小,如果不知道陣列大小,請使用集合。陣列大小一旦指定以後不能改變。用這種形式定義陣列可以不指定陣列大小,但是編譯器會統計得出陣列大小。假如陣列中的元素是引用型別,那陣列中的每個元素必須要分配記憶體,否則丟擲異常。

int []myArr = new int[]{ 4, 7, 8 , 10 };
//等價於int []myArr = new int[4]{ 4, 7, 8 , 10 };
//等價於int []myArr = { 4, 7, 8 , 10 };

記憶體分配情況
引用型別的物件儲存在堆,那物件的各種屬性值肯定也在堆啊。
在這裡插入圖片描述二維陣列的定義

int[,] twodim = new int[3,3];

鋸齒陣列的定義

int[][] jagged = new int[3][];
jagged[0] = new int[2]{1,2};
jagged[1] = new int[6]{3,4,5,6,7,8};
jagged[2] = new int[3]{9,10,11};

四十七
Array類
Array類是一個抽象類,可以用CreateInstance()方法建立陣列,用SetValue()方法設定值,用GetVlaue()方法獲取值。
四十八
複製陣列
C#的陣列有Clone()方法可以用來複制陣列,如果陣列元素是值型別,則複製值;如果陣列元素是引用型別,則複製引用,這就有複製的引用指向相同的物件。
四十八
三元運算子
?號左邊是一個bool表示式,假如為true,則整個式子的返回值是"boy",假如為false,則整個式子的返回值是"girl"。

a==b?"boy":"girl"

四十九
is運算子
is運算子可以用來檢查物件(值)是否與指定型別相容。

if(“JACK” is string)
{
	Console.Write(“ TRUE ”);
}

五十
as運算子
as運算子用來執行引用型別的顯式型別轉換,如果相容,則轉換成功;如果不相容,則轉換就會返回null。
五十一
sizeof運算子
sizeof運算子用來確定值型別的長度,以位元組為單位。
五十二
可空型別
在型別後面加上 ? 號就代表該型別為可空型別,就是可以為null值。在含有可空型別的一元或者二元運算中,只要有一個運算元為null結果就是null;而在比較可空型別時,只要有一個運算元為null,結果就是false。
五十三
空合併運算子??
空合併運算子在兩個運算元之間,第一個運算元必須是可空型別或者引用型別,第二個運算元必須與第一個運算元型別相容。如果第一個運算元不是null,整個表示式等於第一個運算元的值;如果第一個運算元是null,則整個表示式等於第二個運算元的值。

int? a = null;
int b;
b = a ?? 10;//b has the value 10
a = 3;
b= a ?? 10;//b has the value 3

五十四
整數型別的型別隱式型別只能從位元組數較小的型別轉換為位元組數較大的型別;整數和浮點數之間也存在型別隱式轉換,只要無符號變數在有符號變數的範圍內,那無符號變數也可以隱式轉換成有符號變數。
可空型別可以隱式轉換成其他可空型別,但要遵循規則,同樣,非可空型別也可以隱式轉換成可控型別,同樣要遵守規則。可空型別不能隱式轉換成非可空型別。
強制型別轉換(型別顯式轉換cast)要小心使用,可能會引發異常。
五十五
裝箱,用於把一個值型別資料轉換為引用型別,這個轉換可以顯示也可以隱式。
拆箱用於把以前裝箱的值型別強制轉換為值型別,這個轉換隻能是顯示的。
五十六
有四種比較引用型別相等性的方法。

  1. ReferenceEquals()
    是一個靜態方法,所以不能重寫。比較的是兩個引用是否指向同一塊記憶體地址。
  2. 虛擬的Equals()
    比較的是引用,可以重寫。
  3. 靜態的Equals()
    高階版本的虛擬的Equals()方法,它用來比較物件,可以比較有null的情況。假如兩個引數都是null,則返回true;假如其中一個為null,則返回false;假如都不是null,則呼叫虛擬的Equals()。
  4. 比較運算子 ==
    當比較引用型別時,是比較他們的引用,除了System.String類外,因為它的物件比較的是值(Microsoft重寫了這個運算子)。
    比較值型別可以用Equals()和比較運算子 ==來比較值。
    五十七
    運算子過載
    C#要求運算子過載要成對過載比如>=和<=,==和!=
    五十八
    委託是型別安全的類,它定義了返回型別和引數的型別。
    委託的宣告
delegate void IntMethodInvoker(int x);//說明作為引數的方法返回型別為void,有一個int型別的引數。

委託的呼叫

private delegate string GetString();
static void Main()
{
	int x = 40;
	GrtString myString =new GetString(x.Tostring);
	Console.WriteLine("String is {0}" , myString());
}

委託示例

//MathOperations.cs
class MathOperations
{
	public static double MultiplyByTwo(double value)	
	{	
		return value*2;
	}
	public  static double Square(double value)
	{	
		return value*value;
	}
}
//Program.cs
using System;
namespace Workx.ProCSharp.Delegates
{
	delegate double DoubleOp(double x);
	class Program
	{	
		DoubleOp[] operations=
		{
			MathOperations.MultiplyByTwo,
			MathOperations.Square
		};
		for(int i = 0;i < operations.Length ; i++)
		{
			Console.WriteLine("Using operations[{0}] : ",i);
			ProcessAndDisPlayNumber(operations[i],2.0);
			ProcessAndDisPlayNumber(operations[i],7.4);
			ProcessAndDisPlayNumber(operations[i],1.414);
			Console.WriteLine();
		}
	}
	static void ProcessAndDisplayNumber(DoubleOp action,double value)
	{	
		double result = action(value);
		Console.WriteLine("Value is {0},result of operation is {1}",value,result);
	}
}

五十九
重要泛型委託Action和Func
泛型委託Action表示引用一個void返回型別的方法。這個委託可以引用帶有至少1個至多16個引數型別的方法。
Action表示引用帶有一個泛型引數的方法,Action<in T1,in T2>表示引用帶有兩個泛型引數的方法,以此類推。
泛型委託Func允許引用帶返回型別的方法。可以引用帶有至少0個至多16個引數型別的方法。
Func表示引用帶有0個引數和具有返回型別的方法,Func<in T,out TResult>表示引用帶有一個泛型引數和具有返回型別的方法。

//MathOperations類如【五十八】定義
Func<double,double>[] operations = 
{
	MathOperations.MultiplyByTwo,
	MathOperations.Square
};
//則ProcessAndDisplayNumber()如下
static void ProcessAndDisplayNumber(Func<double,double> action,double value)
{	
	double result = action(value);
	Console.WriteLine("Value is {0},result of operation is {1}",value,result);
}

六十
多播委託
可以用委託陣列來包含對多個方法的引用,也可以使用多播委託來實現。呼叫多播委託,可以順序連續呼叫多個方法,但是多播委託必須是以void為返回型別的。多播委託支援 + 和+=來新增引用,也支援 - 和 -= 去除引用。

Action<double> operations = MathOperations.MultiplyByTwo;
operations += MathOperations.Square;

使用多播委託時可以用GetInvocationList()方法返回陣列型別Delegate[]物件。
六十一
lambda表示式的運算子 => 讀作 “goes to”,它的左邊定義了需要的引數,右邊則是使用了左邊引數的方法實現。

//EXP 1
Func<string,string> oneParam = s=>String.Format("change uppercase {0}",s.ToUpper());
//EXP 2
Func<double,double,double> twoParam=(x,y)=>x * y;
//EXP 3
Func<string,string> lambda = param=>
{
	param+=1;
	return param;
}

六十二
通過lambda表示式可以訪問lambda表示式塊外部的變數,這稱為閉包。
六十三
事件基於委託,例如Button的Click事件,這類事件就是委託。

相關文章