CLR via C#:委託

使用委託:使用流程如下所示:
1.使用delegate關鍵字來定義委託類型。編譯器會使該委託類型繼承自MulticastDelegate,並提供構造函數,同步調用回調函數Invoke,異步調用回調函數BeginInvoke和EndInvoke。參考代碼如下所示:

// 使用delegate關鍵字來定義委託類型MyDelegate
public delegate float MyDelegate(double pd, int pi, string ps, object po);
// 編譯器生成完整的委託類型MyDelegate
public class MyDelegate : System.MulticastDelegate
{
	// 構造函數
	public MyDelegate(Object @object, IntPtr method);
	// 同步調用回調函數
	public virtual float Invoke(double pd, int pi, string ps, object po);
	// 異步調用回調函數
	public virtual IAsyncResult BeginInvoke(double pd, int pi, string ps, object po, AsyncCallback callback, Object @object);
	public virtual float EndInvoke(IAsyncResult result);
}

2.定義的回調函數要跟委託類型的簽名保持一致,並支持引用類型的協變性(函數返回類型爲子類型)和逆變性(函數參數類型爲基類型)。
3.使用new關鍵字來創建委託實例,並綁定回調函數。具體流程如下:
1>.獲取委託類型的構造函數所需要的object參數值。當回調函數是靜態函數時,object設置爲null;否則object設置爲實例函數隱藏的this。
2>.獲取委託類型的構造函數所需要的method參數值。該字段值爲回調函數在MethodDef或者MethodRef元數據中的token值。
3>.調用委託類型的構造函數,將_target字段設置爲object參數值;將_methodPtr字段設置爲method參數值;將_invocationList字段設置爲null。
4.以調用函數的方式來調用委託實例時,實際上是調用委託實例的Invoke函數。在Invoke函數內部會判斷_invocationList字段是否爲空。當_invocationList字段爲空時,就調用_methodPtr對應的函數,並且將_target作爲參數傳遞給該函數隱藏的this;否則就遍歷_invocationList字段,並以調用函數的方式來調用每個委託實例。
Invoke函數參考僞代碼如下所示:

public virtual 委託類型的返回類型 Invoke(委託類型的參數列表)
{
	委託類型的返回類型 result;
	Delegate[] delegateSet = _invocationList as Delegate[];
	if (delegateSet != null)
	{
		foreach(委託類型 d in delegateSet)
		{
			// 以調用函數的方式來調用每個委託實例
			result = d(委託類型的參數列表);
		}
	}
	else
	{
		// 調用_methodPtr對應的函數,並且將_target作爲參數傳遞給該函數隱藏的this
		result = _methodPtr.Invoke(_target, 委託類型的參數列表);
	}
	
	return result;
}

調用委託實例參考僞代碼如下所示:

返回值 = 委託實例(參數列表);
// 等價於
返回值 = 委託實例.Invoke(參數列表);

使用委託鏈:使用流程如下所示:
1.使用Delegate的Combin函數(等價於+=操作符)來將委託實例添加到委託鏈中。編譯器會執行以下操作:
1>.當兩個參數代表的委託實例都爲null時,函數就會返回null。
2>.當兩個參數代表的委託實例中只有一個爲null時,函數就會返回不爲null的參數代表的委託實例。
3>.當兩個參數代表的委託實例都不爲null時,函數會返回一個新的委託實例。具體流程如下:
1>>.創建一個新的委託實例。
2>>.新委託實例的_invocationList字段會引用一個長度爲2的倍數的委託數組。
3>>.遍歷兩個參數代表的委託實例。當參數代表的委託實例的_invocationList字段爲空時,就將參數代表的委託實例添加到新委託實例的_invocationList字段中;否則就將參數代表的委託實例的_invocationList字段中的所有委託實例都添加到新委託實例的_invocationList字段中。
4>>.返回新委託實例。
2.使用Delegate的Remove函數(等價於-=操作符)來將委託實例從委託鏈中移除。編譯器會執行以下操作:
1>.當第一個參數代表的委託實例爲null時,函數就會返回null。
2>.當第二個參數代表的委託實例爲null時,函數會返回第一個參數代表的委託實例。
3>.當兩個參數代表的委託實例都不爲null時,會執行以下流程:
1>>.當第一個參數代表的委託實例的_invocationList字段爲空時,就會判定第一個參數代表的委託實例的_target和_methodPtr字段值跟第二個參數代表的委託實例的_target和_methodPtr字段值是否相同,如果相同就會返回null;否則就返回第一個參數代表的委託實例。
2>>.當第一個參數代表的委託實例的_invocationList字段不爲空時,就會從尾到頭的遍歷該_invocationList字段,並執行以下流程:
1>>>.當查找到一個委託實例的_target和_methodPtr字段值跟第二個參數代表的委託實例中的_target和_methodPtr字段值相同時,就會結束遍歷,同時從_invocationList字段中移除查找到的委託實例。當該_invocationList字段長度爲1時就直接返回該_invocationList字段中的委託實例;否則就新創建一個委託實例,並將該_invocationList字段中所有的委託實例添加到新委託實例的_invocationList字段中,最後返回新委託實例。
2>>>.當沒有查找到一個委託實例的_target和_methodPtr字段值跟第二個參數代表的委託實例中的_target和_methodPtr字段值相同時,就會返回第一個參數代表的委託實例。
3.使用Delegate的GetInvocationList函數從委託鏈實例中獲取委託實例數組。編譯器會執行以下操作:
1>>.新創建一個委託實例數組。
2>>.當委託鏈實例的_invocationList字段爲空時,就將委託鏈實例添加到新委託實例數組。
3>>.當委託鏈實例的_invocationList字段不爲空時,就將_invocationList字段中所有的委託實例添加到新委託實例數組,
4>>.返回新委託實例數組。
4.如果執行第3步驟的話,就對新的委託實例數組中的每個委託實例都執行使用委託中的第4步驟; 否則就對委託鏈實例執行使用委託中的第4步驟。

定義委託:具有以下原則:
1.儘量使用Action(沒有返回值,最大支持16個參數)以及Func(有返回值,最大支持16個參數)泛型委託,這樣可以減少系統中委託類型的個數。
2.委託類型應該支持逆變性和協變性,這樣可以使委託類型支持更多的情形。

委託的簡化語法:爲了方便開發人員理解和使用委託,C#爲委託提供了很多簡化語法,但是編譯器最終還是會生成非簡化版本的IL代碼。常見的簡化語法如下所示:
1.構造委託實例時,可以不用new關鍵字來構建,而是通過指定回調函數來構建。參考的僞代碼如下所示:

委託類型 變量名 = new 委託類型(回調函數);
等價於
委託類型 變量名 = 回調函數;

2.傳遞迴調函數來構造委託實例時,可以不用傳遞具體的回調函數,而是通過lambda表達式來傳遞匿名回調函數。
匿名回調函數具有以下特性:
1>.匿名回調函數沒有參數時,=>左側用"()“來表示。
2>.匿名回調函數只有一個參數時,=>左側可以用”(參數類型 參數變量)“來表示,也可以用”(參數變量)“來表示,還可以用"參數變量"來表示。
3>.匿名回調函數有多個參數時,=>左側可以用”(參數類型1 參數變量1, …)“來表示,也可以用”(參數變量1, …)“來表示。
4>.匿名回調函數有返回值且執行體只有一句時,=>右側可以用”{return 返回值;}“來表示,也可以用"返回值"來表示。
5>.匿名回調函數有返回值且執行體有多句時,=>右側用”{ …; return 返回值;}“來表示。
6>.匿名回調函數沒有返回值且執行體只有一句時,=>右側可以用”{執行語句;}“來表示,也可以用"執行語句"來表示。
7>.匿名回調函數沒有返回值且執行體有多句時,=>右側用”{執行語句; …}"來表示。
創建委託實例的流程如下:
1>.編譯器會生成一個輔助類。
2>.輔助類面會定義一個用於委託實例調用的回調函數。
3>.如果匿名函數有訪問上層的局部變量的話,該輔助類裏面會定義跟訪問上層的局部變量相同名稱的成員字段。
4>.創建輔助類實例並用訪問上層的局部變量的值來初始化相關的成員字段。
5>.創建一個委託實例,並綁定輔助類實例中的回調函數。

委託和反射:使用反射可以在不知道委託的所有必要信息的前提下創建&使用委託實例。流程如下所示:
1.使用TypeInfo實例的GetDeclaredMethod函數來創建指定函數名的MethodInfo實例。參考僞代碼如下所示:

MethodInfo mi = typeof(DelegateReflection).GetTypeInfo().GetDeclaredMethod(函數名);

2.使用MethodInfo實例的CreateDelegate函數來創建指定委託類型的實例。參考僞代碼如下所示:

// 當target值爲空時,回調函數爲靜態函數;否則回調函數爲實例函數。
Delegate d = mi.CreateDelegate(委託類型, target值);

3.通過委託實例的DynamicInvoke函數來調用綁定的回調函數。參考僞代碼如下所示:

// 參數列表會傳遞給委託實例綁定的回調函數。如果該回調函數是實例函數時,還會傳遞target參數值到該函數中。
返回值 = d.DynamicInvoke(參數列表);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章