zoukankan      html  css  js  c++  java
  • [CLR via C#]委托

         委托是一种新的面向对象语言特性,委托的功能是在CLR的支持下实现的,这就意味着它并不受限于特定的编程语言,比如C#使用delegate关键字来定义委托,其他的.NET编程语言可以使用自己的方式来定义委托。

         以委托作为基础,.NET构造了一个技术大厦,事件驱动、异步调用和Lambda表达式都建立于委托之上,还有许多其他的技术与委托有着密切的联系,掌握委托是探索这些技术领域的前提。

    一、委托的简单使用

    namespace ConsoleApplication1
    {
        public delegate int MathOptDelegate(int x, int y);
    
        class Program
        {
            static void Main(string[] args)
            {
                MathOptDelegate mathOpt = Add;
    
                int z = mathOpt(3, 2);
            }
    
            private static int Add(int x, int y)
            {
                return x + y;
            }
        }
    }

    从上例可以直观的感受到:委托可以看成是一个方法的"容器",将某一具体的方法"装入"后,就可以把它当成方法一样使用。但是不是所有的方法都可以赋值给mathOpt变量,必须满足一定的条件,定义委托类型时对方法的要求被称为方法的"签名"。

    二、深入探索委托技术内幕

    1.详解委托类型

    用ILDASM打开编译后的项目文件,可以查看代码生成的IL指令。

    不难发现使用delegate关键字定义一个委托类型时,其实是定义了一个新类MathOptDelegate,此类派生自MulticastDelegate,而MulticastDelegate又派生自Delegate。可以看到有一个构造函数和Invoke方法,继续查看Main方法,如下图所示:

    查看IL_0007代码,发现调用了新生成的MathOptDelegate的构造函数,查看IL_0010代码,发现调用了MathOptDelegate的Invoke方法。所以通过委托变量间接调用方法,实际调用的是MathOptDelegate对象的Invoke方法。

    通过查看IL代码,可以看到委托的真实面目,对于以下这条委托定义语句:

    public delegate int MathOptDelegate(int x, int y);

    C#编译器实际上是按照以下这个"代码模板"进行编译的:

    public class MathOptDelegate : System.MultiDelegate
    {
        public MathOPtDelegate(Object target,Int32 methodPtr);
    
        public Int32 virtual Invoke (Int32 value1,Int32 value2);
    
        public virtual IAsyncResult BeginInvoke(Int32 value1,Int32 value2, AsyncCallback callback,Object object);
    
        public virtual Int32 EndInvoke(IAsyncResult result);
    
    }

    注意MathOptDelegate类的构造函数,它接收两个参数target和methodPtr。

    target:引用要调用方法的对象,如果调用的是静态方法,则target = null。

    methodPtr:是一个方法指针,代表要调用的对象方法

    2.委托调用列表

    如果委托仅仅是方法调用的另一种方式,那何必多此一举引入"委托"这一特性?直接调用方法不更简单明了?对此问题的回答是:委托变量不仅可以引用一个方法,还可以组合多个方法并批量执行它们。

    namespace ConsoleApplication1
    {
        public delegate int MathOptDelegate(int x, int y);
    
        class Program
        {
            static void Main(string[] args)
            {
                MathOptDelegate a = Add;
    
                MathOptDelegate b = Divide;
    
                MathOptDelegate c = a + b;
    
                MathOptDelegate d = c - a;
    
                Console.ReadKey();
            }
    
            private static int Add(int x, int y)
            {
                return x + y;
            }
    
            private static int Divide(int x, int y)
            {
                return x / y;
            }
        }
    }

    Delegate定义了一个GetInvocationList静态方法用于获取委托调用列表(委托调用列表其实是委托对象内部所包容的一个数组,存放在数组中的元素是Delegate类型的实例,每个实例引用一个静态或实例方法),如果在代码中调用委托变量,将导致委托调用列表中的所有方法顺序执行,如果委托定义的方法有返回值,则多路委托变量的返回值为委托调用列表中最后一个方法的返回值。

    但是委托创建之后,它的委托调用列表是不可更改的。使用"+"或者"-"运算符合并或分割两个委托调用列表,得到的其实是一个新的委托调用列表,可以参考字符串是不可更改的情况。

    三、使用预定义的委托

    .NET为了方便开发人员的使用,预先定义了几种委托,分别是Action、Function以及Predicate,使用起来都比较简单,所以就不再多介绍。

    四、匿名方法和Lambda表达式

    使用委托有几个步骤:

    1)定义委托类型:

    2)定义一个或多个符合委托类型要求的方法;

    3)定义委托类型的变量;

    4)将第2步定义的方法引用"挂接"到第3步定义的变量,以构建一个"委托调用列表";

    5)通过委托变量"间接"调用委托调用列表;

    显然,上述步骤很麻烦,能否简化?比如赋值给委托变量的方法只在这一个地方用到,其他的地方都不会调用它,为何需要单独定义成一个方法。基于上述简化开发的考虑,C#引入了"匿名方法"和"Lambda表达式"。

    1.匿名方法揭秘

    namespace ConsoleApplication1
    {
        public delegate int MathOptDelegate(int x, int y);
    
        class Program
        {
            static void Main(string[] args)
            {
                MathOptDelegate a = delegate(int x, int y)
                {
                    return x + y;
                };
    
                Console.WriteLine(a(2, 3));
    
                Console.ReadKey();
            }
    
        }
    }
    

    从示例中可以看到,"匿名方法"其实是将方法定义与委托变量赋值两个步骤合在一起,从而省掉了单独定义一个方法的麻烦。

    使用ildasm查看上述代码生成的文件,会发现C#编译器自动生成了一个静态的方法:

    2.奇特的Lambda表达式

    上述示例中给委托变量赋值的语句

    MathOptDelegate a = delegate(int x, int y){ return x + y; };

    如果使用Lambda表达句,可以进一步简化: MathOptDelegate a  = (x,y) => {return x + y};

    所以:Lambda表达式其实就是匿名方法的进一步简化,可以用于定义一个匿名函数,交将其传递给一个委托变量。

  • 相关阅读:
    使用apt-mirror建立本地debian仓库源
    在MacOS上利用docker构建buildroot
    mac开发错误:errSecInternalComponent
    NFS作为根文件系统,挂载超时
    关于物体 '固有类别' 与 '实际使用类别' 分离的情况,结构体定义方法
    Crontab could not create directory .ssh
    mac bash_profile
    Mac bash rc
    watchtower无法自动更新镜像的解决方法
    spring security oAuth2.0 数据库说明
  • 原文地址:https://www.cnblogs.com/JustYong/p/4769204.html
Copyright © 2011-2022 走看看