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  包建强  Views(1774)  Comments(1Edit  收藏  举报