CLR笔记:15.委托
Delegate是方法地址的指针,而且不区分static和instance方法。
Delegate是定义在Class之外的,这个平级的Class中包括Delegate要使用的方法。
2.Delegate允许引用类型的协变(covariance)和反协变(contra-variance)。即:
string SomeOtherMethod(Stream s)
{
}
"协变"指对于返回类型,方法可以派生于委托。
"反协变"指对于参数类型,委托可以派生于方法。
3.委托的方法回调一般这样写:
{
if (d != null)
{
d(p1, p2);
}
}
这里我补充一句废话:使用CallBack方法是可以传入null值给TestDele参数的;CallBack方法中要判断d值是否为空,null就不能执行方法。
4.Delegate本质:一个类
以下委托声明:
等价于这个类:
{
public TestDele(int intTest, IntPtr method);
public virtual string Invoke(int intTest);
public virtual IAsyncResult BeginResult(int intTest, AsyncCallback callback, Object object);
public virtual void EndInvoke(IAsyncResult result);
}
委托的继承关系:如图
MulticastDelegete中有3个字段非常重要:
字段 | 类型 | 描述 |
_target | System.Object | 静态方法时为null;实例方法时为实例对象。对外表现为属性Target |
_methodPtr | System.IntPtr | 一个内部整数值,用来标志回调方法。对外表现为属性Method,但此时已将整数转换为MemberInfo对象 |
_invocationList | System.Object | 通常是null,在“委托链”中形成一个数组 |
因为_target和_methodPtr对外表现为属性Target和Method,所以可以利用这个信息,
1)检查一个委托是否引用一个特定类型的实例方法:
{
return ( (d.Target != null) && (d.Target.GetType() == type) );
}
2)检查回调方法是否有特定的名称:
{
return (d.Method.Name = methodName)
}
回到3.回调方法中
d(p1, p2); 等价于 d.Invoke(p1, p2),后者是这句话在委托类中的本质。
5.委托链回调多个方法
Delegate类提供两个静态方法Combine和Remove,
public static Delegate Remove(Delegate source, Delegate value);
使用如下:
FeedBack fb1 = new FeedBack(TestClass.Function);
FeedBack fb2 = new FeedBack(TestClass.Function2);
fbChain = (FeedBack)Delegate.Combine(fbChain, fb1);
fbChain = (FeedBack)Delegate.Combine(fbChain, fb2);
只有一个委托时,_invocationList指向null;多于一个委托时形成委托链,产生一个委托对象数组,_invocationList会指向这个数组。
对于Remove方法,如果移除后委托链中不再有对象,则返回null,也就是说,_invocationList中允许有一个元素的存在(不同于Combine处)
注:对于void型委托,委托链会按照队列顺序依次执行方法;对于有返回值的委托,委托链返回最后一个委托的返回值。
在C#中,相应的提供+=与-=来代替Combine和Remove。
6.MulticastDelegate的GetInvocationList()实例方法
如果链中一个方法抛出异常,就会停止调用后续对象——这个算法不够好。
为了避免这个问题,即使遇到异常,抛出后,继续调用后续的委托方法,可以使用GetInvocationList():
比如:delegateList为一个委托链,那么通过delegateList.GetInvocationList()就可以得到一个委托数组arrayDelegates,遍历这个数组,在其中使用try...catch...捕获异常,从而所有的委托方法都可以调用到。
7. 委托中的反射——CreateDelegate()方法与DynamicInvoke()方法
使用委托,是因为在编译时不知道要使用哪个回调方法,所以说委托是函数指针。
但如果在编译时,连要使用哪个委托都不知道,或者不知道必须要传递哪些参数,这时候就需要反射的帮助了。
Delegate提供两个方法:
CreateDelegate(),有若干重载:
public static Delegate CreateDelegate(Type type, MethodInfo method);
public static Delegate CreateDelegate(Type type, MethodInfo method, bool throwOnBindFailure);
//For 实例方法委托:
public static Delegate CreateDelegate(Type type, object firstArgument, MethodInfo method);
public static Delegate CreateDelegate(Type type, object firstArgument, MethodInfo method, bool throwOnBindFailure);
这里type参数为具体delegate类型,method参数为回调方法名称,如果有问题就根据throwOnBindFailure判断是否抛出异常,不抛出异常时就返回null;对于实例方法委托,还要额外传入firstArgument这个实例方法的对象。
可以通过反射获取type参数和method参数,如下:
Type delType = Type.GetType("delegate类型");
以静态方法为例:MethodInfo mi = typeof(静态方法所在类).GetMethod("静态方法名", BindingFlags.NonPublic | BindingFlags.Static)
DynamicInvoke(params Object[] args),实例方法,传递一组在运行时能确定的参数args。
这样,伪代码大致如下:
d.CreateDelegate(delType, mi);
d.DynamicInvoke(params);
8.匿名方法——优雅的委托语法
书上说,使用匿名方法有一个尺度:如果回调方法中多于3行代码,就不要使用。
我认为,不管几行,我都不会去用——能看懂别人写的匿名方法就可以。也许这就是BS程序员与CS程序员的区别吧。
书中总结了4条,逐条分析:
在下面的示例中,我们看到,SomeAsyncTask()方法作为参数传递,而ThreadPool.QueueUserWorkItem()需要的是一个WaitCallback委托对象参数,.NET可以自动推断,将方法解析为委托。以下方法1和方法2其实是一样的。
{
public static void CallbackWithoutNewingADelegateObject()
{
//方法1
WaitCallback waitCallback = new WaitCallback(SomeAsyncTask);
ThreadPool.QueueUserWorkItem(waitCallback, 5);
//方法2
ThreadPool.QueueUserWorkItem(SomeAsyncTask, 5);
}
private static void SomeAsyncTask(Object o)
{
Console.WriteLine(o);
}
}
第2条,不需要定义回调方法——"匿名方法"由此而来
{
//方法3,于是可以省略SomeAsyncTask方法
ThreadPool.QueueUserWorkItem(
delegate(Object obj) { Console.WriteLine(obj); },
5);
}
匿名方法中可以有任意行代码,以上只是一行Console输出语句
第3条,不需要指定回调方法的参数——不使用方法的参数时,可以省略参数部分
这一条是由第2条演变而来,如下示例:
{
MessageBox.Show("Hello!");
};
//简写为
button1.Click += delegate
{
MessageBox.Show("Hello!");
};
因为sender和e两个参数并不使用,所以省略之,但是CLR仍然会在编译时自动生成这两个参数。如果匿名方法中使用了其中任一个参数,还是要全部声明。
第4条,在回调方法中可以使用外部的变量
还是基于第2条,因为有了匿名方法,从而可以在其中使用外部的一些变量,这样就不必多建很多参数传来传去的了。