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(参数列表);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章