• <NET CLR via c# 第4版>笔记 第17章 委托


    17.1 初识委托

    • .net 通过委托来提供回调函数机制.
    • 委托确保回调方法是类型安全的.
    • 委托允许顺序调用多个方法.

    17.2 用委托回调静态方法

    • 将方法绑定到委托时,C# 和 CLR 都允许引用类型的 协变性(covariance)逆变性(contravariance).
    • 协变性是指方法能返回从委托的返回类型派生的一个类型.
    • 逆变性是方法获取的参数可以是委托的参数类型的基类.
    • 只有引用类型才支持协变性与逆变性,值类型或 void 不支持
        delegate object MyCallback(FileStream s);
    
        //可以应用于委托 MyCallback
        string SomeMethod(Stream s);
    
        //返回值是值类型,不能应用于委托 MyCallback
        int SomeOtherMethod(Stream s)
    

    17.3 用委托回调实例方法

    17.4 委托揭秘

    • 编译器看到委托后,会生成一个同名的类,并继承于 System.MulticastDelegate
    • System.MulticastDelegate 派生自 System.Delegate
    • System.MulticastDelegate的三个重要的非公共字段:
      • target System.Object 当委托对象包装一个静态方法时,这个字段为 null .当委托对象包装一个实例方法时,这个字段引用的是回调方法要操作的对象.
      • methodPtr System.IntPtr 一个内部的整数值, CLR 用它标识要回调的方法.
      • invocationList System.Object 该字段通常为 null, 构造委托链时它引用一个委托数组.

    17.5 用委托回调多个方法( 委托链 )

    • 使用 Delegate 类的公共静态方法 Combine 将委托添加到链中:
        fbChain=(Feedback) Delegate.Combine(fbChain, fb1);
    
    • 使用 Delegate 类的公共静态方法 Remove 从链中删除委托. 每次 Remove 方法调用只能从链中删除一个委托.
        fbChain=(Feedback) Delegate.Combine(fbChain, fb1);
    
    • 委托public delegate int Feedback(int value)Invoke 的伪代码:
        public int Invoke(int value) {
            int result;
            Delegate[] delegateSet = _invocationList as Delegate[];
            if (delegateSet != null)
            {
                // 这个委托数组指定了应该调用的委托
                foreach (Feedback d in delegateSet)
                    result = d(value);  //调用每个委托
            }
            else {  //否则就不是委托链
                //该委托标识了要回调的单个方法,
                //在指定的目标对象上调用这个回调方法
                result = _methodPtr.Invoke(_target,value);
                //上面这行代码接近实际的代码
                //实际发生的事情用C#是表示不出来的
            }
            return result;
        }
    

    17.5.1 C# 对委托链的支持

    • c#编译器自动为委托类型的实例重载了+=和-=操作符.这些操作符分别调用 Delegate.CombineDelegate.Remove.
        fbChain += fb1;
        fbChain += fb2;
        fbChain -= fb1;
    

    17.5.2 取得对委托链调用的更多控制

    • 可以通过实例方法 GetInvocationList 获取委托链中委托的集合,然后通过自定义算法,显式调用这些委托.

    17.6 委托定义不要太多( 泛型委托 )

    • 尽量使用 Action<T>Func<T> 委托.需要使用 ref ,out ,params 关键字的地方除外.

    17.7 C# 为委托提供的简化语法

    • 后面描述的这些只是 C# 的语法糖.

    17.7.1 简化语法1: 不需要构造委托对象

    • 由于c#编译器能自己进行推断,所以可以省略构造 ThreadPool.QueueUserWorkItem 方法中 WaitCallback 委托对象的代码.
        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(SomeAsyncTask, 5);
        }
    
        private static void SomeAsyncTask(object o) {
            Console.WriteLine(o);
        }
    

    17.7.2 简化语法2: 不需要定义回调方法( lambda表达式 )

    • C# 允许使用 Lambda 表达式写回调代码,如:
        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(obj =>Console.WriteLine(obj), 5);
        }
    
    • C# 编译器看到这个 lambda 表达式之后,会在类中自动定义一个新的私有方法,称为匿名函数,如下:
    class Program
    {
        //创建该私有字段是为了缓存委托对象,
        //优点: 不会每次调用都新建一个对象
        //缺点: 缓存的对象永远不会被垃圾回收
        [CompilerGenerated]
        private static WaitCallback <>9_xxxDelegate1;
    
        static void Main(string[] args)
        {
            if(<>9_xxxDelegate1==null)
                <>9_xxxDelegate1 = new WaitCallback(<Main>.b_0);
    
            ThreadPool.QueueUserWorkItem(<>9_xxxDelegate1,5);
        }
    
        [CompilerGenerated]
        private static void <Main>.b_0(object obj) {
            Console.WriteLine(obj);
        }
    }
    
    • 如果调用方法不是静态的,但其内部的 匿名函数 不包含实例成员引用,编译器仍会生成静态匿名函数,因为它的效率比实例方法高; 但如果 匿名函数 的代码确实引用了实例成员,编译器就会生成非静态匿名函数.
    • 下面是一些 lambda 表达式的使用范例:
        //如果委托不获取任何参数,就使用()
        Func<string> f = () => "Jeff";
    
        //如果委托获取一个或更多参数,可显式指定类型
        Func<int, string> f2 = (int n) => n.ToString();
        Func<int, int, string> f3 = (int n1, int n2) => (n1 + n2).ToString();
    
        //如果委托获取一个或更多参数,编译器可推断类型
        Func<int, string> f4 = (n) => n.ToString();
        Func<int, int, string> f5 = (n1, n2) => (n1 + n2).ToString();
    
        //如果委托获取一个参数,可省略 ( 和 )
        Func<int, string> f6 = n => n.ToString();
    
        //如果委托有 ref/out 参数,必须显式指定 ref/out 和类型
        Bar b = (out int n) => n = 5;
    
        //Bar的定义
        delegate void Bar(out int z);
    
    • 如果主体由两个或多个语句构成,必须用大括号将语句封闭.在用了大括号的情况下,如果委托期待返回值,还必须在主体中添加 return 语句.

    17.7.3 简化语法3: 局部变量不需要手动包装到类中即可传给回调方法

    示例代码:

        static void Main(string[] args)
        {
            //一些局部变量
            int numToDo = 20;
            int[] squares = new int[numToDo];
            AutoResetEvent done = new AutoResetEvent(false);
            //在其他线程上执行一系列任务
            for (int n = 0; n < squares.Length; n++)
            {
                ThreadPool.QueueUserWorkItem(obj =>
                {
                    int num = (int)obj;
                    //该任务通常更耗时
                    squares[num] = num * num;
                    //如果这是最后一个任务,就让主线程继续运行
                    if (Interlocked.Decrement(ref numToDo) == 0)
                        done.Set();
                }, n);
            }
    
            done.WaitOne();
    
            for (int n = 0; n < squares.Length; n++)
            {
                Console.WriteLine("Index {0} ,Square={1}", n, squares[n]);
            }
        }
    
    • 对于上面代码,C#编译器会定义一个新的辅助类,这个类要为打算传给 回调代码 的每个值都定义一个字段.
    • 此外,回调代码 还必须定义成辅助类中的实例方法.
      C#编译器会像下面这样重写代码:
        class Program
        {
            static void Main(string[] args)
            {
                //一些局部变量
                int numToDo = 20;
                WaitCallback callback1 = null;
    
                //构造辅助类的实例
                <>c_DisplayClass2 class1 = new <>c_DisplayClass2();
    
                //初始化辅助类的字段
                class1.numToDo = numToDo;
                class1.squares = new int[class1.numToDo];
                class1.done = new AutoResetEvent(false);
    
                //在其他线程上执行一系列任务
                for (int n = 0; n < class1.squares.Length; n++) {
                    if (callback1 == null) {
                        //新建的委托对象绑定到辅助对象及其匿名实例方法
                        callback1 = new WaitCallback(class1.<Main>b_0);
                    }
    
                    ThreadPool.QueueUserWorkItem(callback1,n);
                }
    
                //等待其他所有线程结束运行
                class1.done.WaitOne();
    
                //显示结果
                for (int n = 0; n < class1.squares.Length; n++) {
                    Console.WriteLine("Index {0} ,Square={1}", n, class1.squares[n]);
                }
            }
    
            //为避免冲突,辅助类被指定了一个奇怪的名称.
            //而且被指定为私有的,禁止从Program类外部访问
            [CompilerGenerated]
            private sealed class <>c_DisplayClass2:Object{
                //回调代码要使用的每个局部变量都有一个对应的公共字段
                public int[] squares;
                public int numToDo;
                public AutoResetEvent done;
    
                //公共无参构造器
                public <>c_DisplayClass2{}
                
                //包含回调代码的公共实例方法
                public void <Main>b_0(object obj) {
                    int num = (int)obj;
                    squares[num] = num * num;
                    if (Interlocked.Decrement(ref numToDo) == 0)
                        done.Set();
                }
            }
        }
    
    • 作者给自己定了个规则:如果需要在回调方法中包含3行以上的代码,就不使用 lambda 表达式,而是手写一个方法,并为其分配自己的名称.

    17.8 委托和反射

    可以通过 System.Reflection.MethodInfo 提供的 CreateDelegate 方法,创建一个 Delegate 对象,然后调用该对象的 DynamicInvoke 方法,以实现在运行时动态调用委托.

    //下面是一些不同的委托定义
    internal delegate object TwoInt32s(int n1, int n2);
    internal delegate object OneString(String s1);
    static class Program
    {
        static void Main(string[] args)
        {
            //如果委托在命名空间下,第一个参数一定要是带命名空间的完全限定名
            //args = new[] { "TwoInt32s", "Add", "1", "11" };
    
            if (args.Length < 2)
            {
                Console.WriteLine("Usage: TwoInt32s Add 123 321");
                return;
            }
    
            //将delType实参转换为委托类型
            Type delType = Type.GetType(args[0]);
            if (delType == null)
            {
                Console.WriteLine("Invalid delType argument: " + args[0]);
                return;
            }
    
            Delegate d;
            try
            {
                //将Arg1实参转换为方法
                MethodInfo mi = typeof(Program).GetTypeInfo().GetDeclaredMethod(args[1]);
                //创建包装了静态方法的委托对象
                d = mi.CreateDelegate(delType);
            }
            catch (ArgumentException)
            {
                Console.WriteLine("Invalid methodName argument: " + args[1]);
                return;
            }
    
            //创建一个数组,其中只包含要通过委托对象传给方法的参数
            object[] callbackArgs = new object[args.Length - 2];
    
            if (d.GetType() == typeof(TwoInt32s))
            {
                try
                {
                    //将String类型的参数转换为Int32类型的参数
                    for (int a = 2; a < args.Length; a++)
                        callbackArgs[a - 2] = int.Parse(args[a]);
                }
                catch (FormatException)
                {
                    Console.WriteLine("Parameters must be integers.");
                    return;
                }
            }
            if (d.GetType() == typeof(OneString))
            {
                //只复制String参数
                Array.Copy(args, 2, callbackArgs, 0, callbackArgs.Length);
            }
    
            try
            {
                //调用委托并显示结果
                object result = d.DynamicInvoke(callbackArgs);
                Console.WriteLine("Result = " + result);
            }
            catch (TargetParameterCountException)
            {
                Console.WriteLine("Incorrect number of parameters specified.");
            }
        }
    
    
        private static object Add(int n1, int n2)
        {
            return n1 + n2;
        }
        private static object Subtract(int n1, int n2)
        {
            return n1 - n2;
        }
        private static object NumChars(string s1)
        {
            return s1.Length;
        }
        private static object Reverse(string s1)
        {
            return new String(s1.Reverse().ToArray());
        }
    }
    
  • 相关阅读:
    Python入门day12——文件操作的补充
    day11作业
    Python入门day11——文件处理
    文本操作问题
    Python入门day10——基本数据类型之集合
    day09作业
    Pagination(分页) 从前台到后端总结
    Chrome使用技巧(几个月的心得)
    ASTA存在的问题
    SmartBinding实现DataSet与ListView的绑定及同步显示
  • 原文地址:https://www.cnblogs.com/harry-wang/p/7374514.html
Copyright © 2020-2023  润新知