• 《CLR Via C# 第3版》笔记之(二十四) 委托


    委托是.net中实现回调机制的一种重要技术,尤其在编写服务端程序的时候,更是频繁使用委托。

    主要内容:

    • 委托本质
    • 委托链
    • 动态委托

    1. 委托本质

    委托本质其实就是一个类,基本上在可以定义类的地方都可以定义委托,C#中委托的写法其实只是C#的语法糖。

    如下定义个委托:

    using System;
    
    namespace delegate_test
    {
        internal delegate int Sum(int a, int b);
    }

    用ILSpy可以发现对应的IL代码如下:

    .class private auto ansi sealed delegate_test.Sum
        extends [mscorlib]System.MulticastDelegate
    {
        // Methods
        .method public hidebysig specialname rtspecialname 
            instance void .ctor (
                object 'object',
                native int 'method'
            ) runtime managed 
        {
        } // end of method Sum::.ctor
    
        .method public hidebysig newslot virtual 
            instance int32 Invoke (
                int32 a,
                int32 b
            ) runtime managed 
        {
        } // end of method Sum::Invoke
    
        .method public hidebysig newslot virtual 
            instance class [mscorlib]System.IAsyncResult BeginInvoke (
                int32 a,
                int32 b,
                class [mscorlib]System.AsyncCallback callback,
                object 'object'
            ) runtime managed 
        {
        } // end of method Sum::BeginInvoke
    
        .method public hidebysig newslot virtual 
            instance int32 EndInvoke (
                class [mscorlib]System.IAsyncResult result
            ) runtime managed 
        {
        } // end of method Sum::EndInvoke
    
    } // end of class delegate_test.Sum

    从中我们可以看出

    1. 委托是一个类,继承于System.MulticastDelegate。
    2. 委托类中有3个方法和一个构造函数
    3. 委托构造函数中需传入2个参数

    为了更好的了解委托的类,下面写了个简单的例子先来了解一下委托的构造函数:

    using System;
    
    namespace delegate_test
    {
        internal delegate int Sum(int a, int b);
        
        class Program
        {
            public static void Main(string[] args)
            {
                Console.WriteLine("Hello World!");
                
                // 实例化委托,这里和委托的IL代码中构造函数不一致
                // IL代码中 Sum 的构造函数需要传入2个参数(一个object 类型,一个 native int类型)
                /*
                .method public hidebysig specialname rtspecialname 
                    instance void .ctor (
                            object 'object',
                            native int 'method'
                        ) runtime managed 
                    {
                    } // end of method Sum::.ctor
                */
                Sum s = new Sum(Add);
                
                Console.Write("Press any key to continue . . . ");
                Console.ReadKey(true);
            }
            
            public static int Add(int a, int b)
            {
                return a+b;
            }
        }
    }

    注意代码的注释,我们看出C#中实例化委托的写法与IL中看到的委托的构造函数不一致,

    其实这只是C#的语法糖,为了方便代码编写,将上面的代码编译后,用ILSpy查看IL代码:

        IL_0000: nop
        IL_0001: ldstr "Hello World!"
        IL_0006: call void [mscorlib]System.Console::WriteLine(string)
        IL_000b: nop
        IL_000c: ldnull
        IL_000d: ldftn int32 delegate_test.Program::Add(int32, int32)
        IL_0013: newobj instance void delegate_test.Sum::.ctor(object, native int)

    我们发现,实际运行时,还是构造了2个参数传给了委托的构造函数,一个null,还有一个是函数Add的地址。

    这里的第一个参数object为null的原因是,Add函数是static,不用实例化一个object来调用它。

    下面构造一个非static的函数Add2来看看object的值是否还为null。

    using System;
    
    namespace delegate_test
    {
        internal delegate int Sum(int a, int b);
        
        class Program
        {
            public static void Main(string[] args)
            {
                Console.WriteLine("Hello World!");
                
                // 实例化委托,这里和委托的IL代码中构造函数不一致
                // IL代码中 Sum 的构造函数需要传入2个参数(一个object 类型,一个 native int类型)
                /*
                .method public hidebysig specialname rtspecialname 
                    instance void .ctor (
                            object 'object',
                            native int 'method'
                        ) runtime managed 
                    {
                    } // end of method Sum::.ctor
                */
                Sum s = new Sum(Add);
                // 非static函数 Add2
                Sum s2 = new Sum((new Program()).Add2);
                
                Console.Write("Press any key to continue . . . ");
                Console.ReadKey(true);
            }
            
            public static int Add(int a, int b)
            {
                return a+b;
            }
            
            public int Add2(int a, int b)
            {
                return a+b;
            }
        }
    }

    Sum s2 = new Sum((new Program()).Add2); 这句对应的IL如下:

        IL_0019: newobj instance void delegate_test.Program::.ctor()
        IL_001e: ldftn instance int32 delegate_test.Program::Add2(int32, int32)
        IL_0024: newobj instance void delegate_test.Sum::.ctor(object, native int)

    委托的Invoke函数就是我们实际运行委托时调用的函数,至于BeginInvoke和EndInvoke则是用于异步的情况。

    using System;
    
    namespace delegate_test
    {
        internal delegate int Sum(int a, int b);
        
        class Program
        {
            public static void Main(string[] args)
            {
                Console.WriteLine("Hello World!");
                
                // 实例化委托,这里和委托的IL代码中构造函数不一致
                // IL代码中 Sum 的构造函数需要传入2个参数(一个object 类型,一个 native int类型)
                /*
                .method public hidebysig specialname rtspecialname 
                    instance void .ctor (
                            object 'object',
                            native int 'method'
                        ) runtime managed 
                    {
                    } // end of method Sum::.ctor
                */
                Sum s = new Sum(Add);
                
                Sum s2 = new Sum((new Program()).Add2);
                
                // 调用委托的代码 s(5,7) 其实就是调用委托类的 Invoke方法
                Console.WriteLine("5+7="+ s(5,7));
                // 剥去语法外衣,也可以写成
                Console.WriteLine("5+7="+ s.Invoke(5,7));
                
                Console.Write("Press any key to continue . . . ");
                Console.ReadKey(true);
            }
            
            public static int Add(int a, int b)
            {
                return a+b;
            }
            
            public int Add2(int a, int b)
            {
                return a+b;
            }
        }
    }

    s(5,7) 就是 s.Invoke(5,7) 的简化写法。

    2.  委托链

    委托既然是用于回调函数,那就应该可以一次回调多个函数,形成一个委托链。

    这个委托链中应该还可以动态追加和减少回调函数。

    实验代码如下:

    using System;
    
    namespace delegate_test
    {
        internal delegate void Print_Sum(int a, int b);
        
        class Program
        {
            public static void Main(string[] args)
            {
                Console.WriteLine("Hello World!");
             
                Print_Sum s = null;            
                Print_Sum s1 = new Print_Sum(Add1);            
                Print_Sum s2 = new Print_Sum((new Program()).Add2);
                Print_Sum s3 = new Print_Sum((new Program()).Add3);
                
                s += s1;
                s += s2;
                s += s3;
                
                s(1,2);            
                // 委托链中删除Add2
                s -= s2;
                s(1,2);
                
                Console.Write("Press any key to continue . . . ");
                Console.ReadKey(true);
            }
            
            public static void Add1(int a, int b)
            {
                Console.WriteLine("function Add1.... result is " + (a+b).ToString());
            }
            
            public void Add2(int a, int b)
            {
                Console.WriteLine("function Add2.... result is " + (2*(a+b)).ToString());
            }
            
            public void Add3(int a, int b)
            {
                Console.WriteLine("function Add3.... result is " + (3*(a+b)).ToString());
            }
        }
    }

    运行的结果也预期相符,其实其中的 s += s1;s -= s2; 也是C#的语法糖,实际调用的是System.DelegateCombine方法和Remove方法。

    感兴趣的话,可以看编译后的IL代码。

    3. 动态委托

    动态委托其实应用的并不多,它其实是和反射结合使用的。只有在你无法确定所调用的委托所定义的参数个数和参数类型时,才需要用到动态委托。

    使用动态委托主要就是使用 生成委托实例的 CreateDelegate 方法和 调用委托的 DynamicInvoke 方法。

    using System;
    using System.Reflection;
    
    internal delegate void print_sum(int a, int b);
    internal delegate void print_string(string a);
    
    public class Dynamic_Delegate
    {
        public static void Main(string[] args)
        {
            // 假设要调用的delegate名称,方法名称,方法的参数都是由 args传入的。
            // Main方法中并不知道要调用哪个委托
            // 调用委托print_sum时:
            // delegate_test.exe print_sum Sum 2 3
            // 调用委托print_string时:
            // delegate_test.exe print_string Str "hello delegate!"
            
            // 获取各个参数
            string delegate_name = args[0];
            string method_name = args[1];
            // 由于2种委托的参数不同,所以参数可能是一个,可能是两个
            object[] method_args = new object[args.Length - 2];
            for (int i = 0; i<args.Length-2; i++)
            {
                // print_sum的参数需要转换成int型
                if (delegate_name.Equals("print_sum"))
                    method_args[i] = int.Parse(args[2+i]);
                else
                    method_args[i] = args[2+i];
            }
            
            // 获取委托类型
            Type delegate_type = Type.GetType(delegate_name);
            // 获取方法信息
            MethodInfo mi = typeof(Dynamic_Delegate).GetMethod(method_name, BindingFlags.NonPublic | BindingFlags.Static);
            
            // 根据获取的委托类型和方法信息创建一个delegate            
            Delegate d = Delegate.CreateDelegate(delegate_type, mi);
            
            // 动态调用委托
            d.DynamicInvoke(method_args);
            
            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }
        
        private static void Sum(int a, int b)
        {
            Console.WriteLine("delegate print_sum....... result is : " + (a+b).ToString());
        }
        
        private static void Str(string a)
        {
            Console.WriteLine("delegate print_string.... result is : " + a);
        }
    }

    根据编译成的程序集(我的程序集名称是delegate_test.exe),在命令行中分别输入如下命令来分别调用不同的delegate:

    delegate_test.exe print_sum Sum 2 3
    delegate_test.exe print_string Str "hello delegate!"
  • 相关阅读:
    函数声明、引用
    事件绑定的快捷方式 利on进行事件绑定的几种情况
    BOM的节点方法和属性
    JQuery语法 JQuery对象与原生对象互转 文档就绪函数与window.onload的区别
    JPEG解码:huffman解码
    Quartus 中快速分配器件管脚
    PLL的modelsim仿真
    JPEG解码:桶型寄存器
    JPEG解码:反DCT变换(二)
    JPEG解码:反DCT变换(一)
  • 原文地址:https://www.cnblogs.com/wang_yb/p/3029434.html
Copyright © 2020-2023  润新知