CLR笔记:15.委托

1.Delegate是类型安全的,也就是说,在编译期可以检测出错误;而与之相似的Reflection是类型不安全的。
   Delegate是方法地址的指针,而且不区分static和instance方法。
   Delegate是定义在Class之外的,这个平级的Class中包括Delegate要使用的方法。

2.Delegate允许引用类型的协变(covariance)和反协变(contra-variance)。即:
    delegate object MyCallBack(FileStream fs);

        
string SomeOtherMethod(Stream s)
        
{

        }

可以将SomeOtherMethod方法绑定到MyCallBack委托上。
"协变"指对于返回类型,方法可以派生于委托。
"反协变"指对于参数类型,委托可以派生于方法。

3.委托的方法回调一般这样写:
        void CallBack(string p1, string p2, TestDele d)
        
{
            
if (d != null)
            
{
                d(p1, p2);
            }

        }

这里我补充一句废话:使用CallBack方法是可以传入null值给TestDele参数的;CallBack方法中要判断d值是否为空,null就不能执行方法。

4.Delegate本质:一个类
以下委托声明:

public delegate string TestDele(int intTest);

等价于这个类:

    public class TestDele : System.MulticastDelete
    
{
        
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)检查一个委托是否引用一个特定类型的实例方法:

        bool CheckDeleteTarget(MulticastDelegate d, Type type)
        
{
            
return ( (d.Target != null&& (d.Target.GetType() == type) );
        }

2)检查回调方法是否有特定的名称:

        bool CheckDeleteMethod(MulticastDelegate d, string methodName)
        
{
            
return (d.Method.Name = methodName)
        }

回到3.回调方法中
d(p1, p2);   等价于 d.Invoke(p1, p2),后者是这句话在委托类中的本质。

5.委托链回调多个方法
Delegate类提供两个静态方法Combine和Remove,

        public static Delegate Combine(Delegate a, Delegate b);
        
public static Delegate Remove(Delegate source, Delegate value);

使用如下:

            FeedBack fbChain = null;

            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(),有若干重载:

    //For 静态方法委托:
         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。
这样,伪代码大致如下:

Delegate d;
d.CreateDelegate(delType, mi);
d.DynamicInvoke(
params);

8.匿名方法——优雅的委托语法
书上说,使用匿名方法有一个尺度:如果回调方法中多于3行代码,就不要使用。
我认为,不管几行,我都不会去用——能看懂别人写的匿名方法就可以。也许这就是BS程序员与CS程序员的区别吧。
书中总结了4条,逐条分析:

第1条,直接使用回调函数作为参数,不需要构造委托对象
     在下面的示例中,我们看到,SomeAsyncTask()方法作为参数传递,而ThreadPool.QueueUserWorkItem()需要的是一个WaitCallback委托对象参数,.NET可以自动推断,将方法解析为委托。以下方法1和方法2其实是一样的。
    internal sealed class AClass
    
{
        
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条,不需要定义回调方法——"匿名方法"由此而来

        public static void CallbackWithoutNewingADelegateObject()
        
{
            
//方法3,于是可以省略SomeAsyncTask方法
            ThreadPool.QueueUserWorkItem(
                
delegate(Object obj) { Console.WriteLine(obj); },
                
5);
        }

匿名方法中可以有任意行代码,以上只是一行Console输出语句

第3条,不需要指定回调方法的参数——不使用方法的参数时,可以省略参数部分
这一条是由第2条演变而来,如下示例:

            button1.Click += delegate(Object sender, EventArgs e)
            
{
                MessageBox.Show(
"Hello!");
            }
;

            
//简写为
            button1.Click += delegate
            
{
                MessageBox.Show(
"Hello!");
            }
;

因为sender和e两个参数并不使用,所以省略之,但是CLR仍然会在编译时自动生成这两个参数。如果匿名方法中使用了其中任一个参数,还是要全部声明。

第4条,在回调方法中可以使用外部的变量
还是基于第2条,因为有了匿名方法,从而可以在其中使用外部的一些变量,这样就不必多建很多参数传来传去的了。







 

posted @ 2007-08-05 11:54 Jianqiang Bao Views(941) Comments(1) Edit 收藏

 回复 引用 查看   
#1楼[楼主]2007-08-07 12:28 | 包建强      
以下读书笔记摘自http://stock3745.cnblogs.com/,作为大纲再好不过。

The Chapter 15th Delegate

M:委托链(用于多播委托)=>其实质就是一个委托引用的_invocationList数组,其保存着另外几个委托对象,回调时,遍历此数组回调其中的每一个方法

M:如果委托有返回值,则其得到的结果为最后一个委托的方法的返回值,同时,C#为委托类型实例提供了+=和-=的操作符重载,对应于Combine()和Remove()方法,而委托变量直接回调则是隐藏了Invoke()

Q:如何对委托链进行更多的控制?
A:其可显式调用 Delegate[] GetInvocationList(),它可用于调用其中的每个委托,并使用符合自己的算法


Q:C#中为委托提供了哪些便利语法?
A:1.不需要构造委托对象
2.不需要定义回调方法(可用匿名方法),给出方法体即可,其方法没有任何访问限定符,始终是私有的,其是实例还是静态则取决于是否访问了实例成员(代码超过三行则不使用)
3.不需要指定回调方法的参数(在回调代码未引用任何参数前提下)
4.不需要将局部变量封闭到类,即可将它们传递给回调方法

M:如果在编译时不知道委托的这些必要信息,可以使用反射来创建并调用一个委托,实现如下=>
Delegate CreateDelegate(Type type,MethodInfo info)
Delegate CreateDelegate(Type t,MethodInfo m,Boolean b)

Q:委托的实质是什么?
A:其实质是一个类定义,其提供了=>
1.四个方法 构造器,Invoke(),BeginInvoke(),EndInvoke()
2.三个字段 _target, _invocationList, _methodPtr