zoukankan      html  css  js  c++  java
  • 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条,因为有了匿名方法,从而可以在其中使用外部的一些变量,这样就不必多建很多参数传来传去的了。







     

  • 相关阅读:
    安装go版本
    golang简介
    安装MySQL
    art.dialog.art 中,将子页面窗口中的值传递给父框架中
    Windows7下安装CentOS
    生成uuid
    如何开启win7端口的图文教程
    PHPMailer不能发送邮件
    sql 如果关联表 没有值 设置 默认值
    php array 分页
  • 原文地址:https://www.cnblogs.com/Jax/p/843593.html
Copyright © 2011-2022 走看看