zoukankan      html  css  js  c++  java
  • .NET (C#) Internals: Delegates (1)

    引言

    委托(delegate),这个概念大家应该都知道或许还有一些新人不知道,比如说我就是现在才对delegate有个比较清晰的认识,这里我将深入解析delegate跟大家分享我所知道的,希望能对部分人有所帮助,给大家带来不一样的视角。

    本文涉及主题如下:

    • 1、委托初识
    • 2、委托本质
      • 2.1、委托类
      • 2.2、委托构造器
      • 2.3、委托调用
    • 3、实例化委托的几种方式
      • 3.1、使用new操作符实例化委托
      • 3.2、用方法名实例化委托
      • 3.3、用匿名方法实例化委托
      • 3.4、Lambda表达式实例化委托
    • 4、协变委托与逆协变委托
      • 4.1、协变委托
      • 4.2、逆协变委托

    由于文章比较长,我分为几部分来写,而且文章太长了看起来也比较累。接下来的一篇讲讨论委托链等内容。

    1、委托初识

    我们知道委托是一个引用类型,所以他具有引用类型所具有的通性。他保存的不是实际值,而是保存对存储在托管堆(managed heap)中的对象的引用。那他保存的是对什么的引用呢?委托保存的是对函数(function)的引用。

    对学过C/C++的人,是不是觉得跟函数指针很像呢!其实他们是有区别的,在非托管C/C++中,函数的地址就是一个内存地址。该地址不会携带任何额外的信息,例如函数期望的参数个数、参数类型、函数的返回值类型及函数的调用约定。总之,非托管C/C++中函数指针是非类型安全的。而.NET中的委托是类型安全的,委托会检测他所保存的函数引用是否和声明的委托匹配。下面的代码展示这个:

    代码

    编译它你将会看到如下错误:

    image 图1、证明委托是类型安全的

    而如果你的代码如下,将会正确调用PersonInfo函数而不会报错:

    代码

    Note:与C/C++中的函数指针不同,委托是类型安全的,这点很重要!只有跟委托签名相同的方法才能传给/赋给委托。

    2、委托本质

    在C#中使用delegate关键字定义委托,然后使用我们熟悉的函数调用的语法来调用委托,如上述例子中的cb(“skynet”,23)。在这简单的表象背后,.NET编译器为我们做了什么呢?我们使用ILDasm.exe查看我们上面生成的DelegateTest的exe文件(不报错的那个),如下所示:

    image

    图2、ILDasm查看DelegateTest.exe

    可以知道定义CallBack委托时,编译器为我们做了如下工作,实际上定义任何委托编译器都会做如下工作:

    1. 声明一个类,对应上图中的.class nested public auto ansi sealed。
    2. 该类扩展自System.MutlicastDelegate,对应上图中的extends [mscorlib]System.MutlicastDelegate。
    3. 该类包含一个构造器,对应上图中的.ctor: void(object ,native int)。
    4. 该类包含三个方法,分别是BeginInvoke、EndInvoke、Invoke。

    2.1、委托类

    当我们用delegate关键字声明委托时,编译器自动为我们生成如图2所示的类。类的名字即为委托变量名,访问类型为定义的委托访问类型。如上例中,public delegate void CallBack(string name, int number);定义的委托对应的类为CallBack,访问类型为public,该类继承自[mscorlib]System.MutlicastDelegate。如果我们定义委托的访问类型为private或者protected,则对应的委托类的访问类型为private或者protected。但是任何委托都继承自[mscorlib]System.MutlicastDelegate。

    Note:mscorlib.dll一开始是Microsoft Common Object Runtime Library(微软通用对象运行时库)的首字母缩写。但是当ECMA开始标准化CLR以及部分FCL时,mscorlib.dll正式成为Multilanguage Standard Common Object Runtime Library(多语言标准通用对象运行时库)的首字母缩写。

    MulticastDelegate 拥有一个带有链接的委托列表,该列表称为调用列表,它包含一个或多个元素。在调用多路广播委托时,将按照调用列表中的委托出现的顺序来同步调用这些委托。如果在该列表的执行过程中发生错误,则会引发异常。关于委托链的详细讨论将在本文后面讨论。MulticastDelegate类有如下重要的三个私有字段:(关于MulticastDelegate类,想了解更多

    字段 类型 描述
    _target System.Object 获取委托所表示的方法(继承自 Delegate)。指向回调函数被调用时应该被操作的对象,该字段用于实例化方法的回调。
    _methodPtr System.Int32 获取类实例,当前委托将对其调用实例方法(继承自 Delegate)。其主要用于表示指针或句柄,CLR用它来标识回调方法。
    _prev System.MulticastDelegate 指向另一个委托对象,该字段通常为null。

    现在我们明白了——委托本质上是一个类,所以一个类可以在哪定义,一个委托也就可以在哪定义。

    2.2、委托构造器

    从图2还可以看出委托类包含一个构造器,并且构造器接受两个参数:一个对象引用一个指向回调函数方法的整数。即,分别对应着2.1中所提到的MulticastDelegate类的_target、_methodPtr字段。事实上,MulticastDelegate类的构造器有三个重载,如下:

    1. 2h7wdx6c.protmethod(zh-cn,VS.90).gif2h7wdx6c.CFW(zh-cn,VS.90).gifMulticastDelegate()注意:仅.NET Compact Framework 2.0中支持,后面的版本3.5已经移除了它
    2. 2h7wdx6c.protmethod(zh-cn,VS.90).gifMulticastDelegate(Object target, String method):target——在其上定义 method 的对象,method——为其创建委托的方法的名称。此构造函数从编译器生成的代码所产生的类中调用。
    3. 2h7wdx6c.protmethod(zh-cn,VS.90).gifMulticastDelegate(Type target, String method):target——在其上定义 method 的对象的类型,method——为其创建委托的静态方法的名称。此构造函数是从某个类中调用的,它根据一个静态方法名称以及定义该方法的类的 Type 来生成一个委托。

    上例中,语句CallBack cb = pr.PersonInfo; 就是调用的MulticastDelegate(Object target, String method)方法实例化的委托。

    从构造器也可以看出,每个委托对象实际上是对方法其调用时操作的对象的一个封装。MulticastDelegate类定义了两个只读公有实例属性:TargetMethod。给定一个委托对象的引用,我们可以查询这些属性。Target属性返回一个方法回调时操作的对象引用。如果是静态方法,Target将返回null。Method属性返回一个标识回调方法的System.Reflection.MethodInfo对象。

    2.3、委托调用

    前面说了如何声明委托并用委托构造器实例化,那如何来调用委托呢?我们先来看看上例中main函数的IL代码,如下图所示:

    委托调用

    图3、main函数IL代码

    从图3可以知道,Main函数:1、调用类Program的构造器实例化Program对象;2、实例化Program的PersonInfo方法;3、调用委托CallBack的构造器,参数为object、int,即调用的是2.2中所讲的第二个构造器;4、加载“skynet”,23作为委托的参数,调用委托。

    从Main函数的第4步可以知道实际上是通过Invoke(string,int32)方法调用委托,但注意C#中我们不可以通过Invoke方法显示地调用委托。当Invoke被调用时,它使用_target和_methodPtr两个私有字段来在指定的对象上调用期望的方法。注意Invoke方法的签名和CallBack委托的签名是相匹配的。换句话说,CallBack接受2两个参数且返回void,所以Invoke方法也接受同样的2个参数且返回void。

    事实上,.NET Framework 允许您异步调用任何方法。为此,应定义与您要调用的方法具有相同签名的委托;CLR会自动使用适当的签名为该委托定义 BeginInvoke 和 EndInvoke 方法。说明:.NET Compact Framework 中不支持异步委托调用,也就是 BeginInvoke 和 EndInvoke 方法。

    BeginInvoke 方法启动异步调用。该方法与您需要异步执行的方法具有相同的参数,还有另外两个可选参数。第一个参数是一个 AsyncCallback 委托,该委托引用在异步调用完成时要调用的方法。第二个参数是一个用户定义的对象,该对象将信息传入回调方法。BeginInvoke 会立即返回,而不等待异步调用完成。BeginInvoke 返回一个可用于监视异步调用进度的 IAsyncResult

    EndInvoke 方法检索异步调用的结果。在调用 BeginInvoke 之后随时可以调用该方法。如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成。EndInvoke 的参数包括您需要异步执行的方法的 out 和 ref 参数(在 Visual Basic 中为 <Out> ByRef 和 ByRef)以及由 BeginInvoke 返回的 IAsyncResult

    代码示例(查看)演示了使用 BeginInvoke 和 EndInvoke 进行异步调用的四种常用方法。调用 BeginInvoke 之后,您可以执行下列操作:

    • 进行某些操作,然后调用 EndInvoke 一直阻止到调用完成。
    • 使用 IAsyncResult.AsyncWaitHandle 属性获取 WaitHandle,使用其 WaitOne 方法一直阻止执行直到发出 WaitHandle 信号,然后调用 EndInvoke。
    • 轮询由 BeginInvoke 返回的 IAsyncResult,以确定异步调用何时完成,然后调用 EndInvoke。
    • 将用于回调方法的委托传递给 BeginInvoke。异步调用完成后,将在 ThreadPool 线程上执行该方法。回调方法调用 EndInvoke。

    3、实例化委托的几种方式

    委托虽然是引用类型,也具有引用类型的通性——保存的是托管堆中对象的引用,但是delegate也具有独特之处,除了用new操作符实例化之外,还有用其他几种方法实例化。

    3.1、使用new操作符实例化委托

    跟普通类一样,可以使用new操作符实例化委托,如下代码所示:

    代码

    值得注意的是,new操作符实例化委托时传的参数的一个方法,如上代码所示CallBack cb = new CallBack(pr.PersonInfo)。然而,实际上编译器知道我们正在构造一个委托,它会通过分析源代码来确定我们引用的是哪个对象和方法。其中的对象引用会被传递给target参数,一个特殊的标识方法的Int32值(由MehtodDef或者MethodRef元数据标记获得)会被传递给methodPtr参数。对于静态方法而言,null会被传递给target参数。在构造器内部,这两个参数会被保存在相应的私有字段中。

    委托除了调用实例方法还可以引用静态方法,假如上述示例中PersonInfo方法是静态的,则只需这样调用而不需要先实例一个Program对象:

    代码

    对于委托调用静态方法同样适用于后面的几种实例化委托方法。

    3.2、用方法名实例化委托

    如第一节委托初识中给出的代码就是使用这种方法,这里就不累述了。这种方法相对于匿名委托(见3.3)叫做有名委托。

    3.3、用匿名方法实例化委托

    用匿名方法实例化委托,即将匿名方法赋给委托。注意:匿名方法中的变量的生命周期将扩展到委托的生命周期。代码示例如下:

    代码

    用ILDasm查看匿名方法实例化委托生成的IL代码,可知本质跟有名方法一样,如下图所示:

    image 图4、匿名方法实例化委托

    3.4、Lambda表达式实例化委托

    这是C# 3.0中引入的,Lambda表达式是函数编程(Functional Programming)的核心概念,关于Lambda请自行查阅相关资料。代码示例:

    代码

    4、协变委托与逆协变委托

    在第一个委托初识中我们知道了:委托是类型安全的,只有方法的签名和委托的签名相同时,方法才能传给/赋给委托。但是协变和逆协变为我们提供了一定程度的灵活性。协变允许方法具有的派生返回类型比委托中定义的更多。逆变允许方法具有的派生参数类型比委托类型中的更少。即,委托中的协变只要针对方法及委托的返回值类型而言,而逆变则针对方法及委托中的参数而言。

    4.1、协变委托

    当委托方法的返回类型具有的派生程序比委托签名更大时,就称为协变委托方法。因为方法的返回类型比委托签名的返回类型更具体,所以可对其进行隐式转换,这样该方法就可用作委托。协变使得创建可被类和派生类同时使用的委托方法成为可能。代码示例:

    代码

    4.2、逆协变委托

    当委托方法签名具有一个或多个参数,并且这些参数的类型派生自方法参数的类型时,就称为逆变委托方法。因为委托方法签名参数比方法参数更具体,因此可在传递给处理程序方法时对他们隐式转换。这样逆变使得大量类使用的更通用的委托方法的创建变得更简单。代码示例:

    代码

  • 相关阅读:
    Leetcode Binary Tree Preorder Traversal
    Leetcode Minimum Depth of Binary Tree
    Leetcode 148. Sort List
    Leetcode 61. Rotate List
    Leetcode 86. Partition List
    Leetcode 21. Merge Two Sorted Lists
    Leetcode 143. Reorder List
    J2EE项目应用开发过程中的易错点
    JNDI初认识
    奔腾的代码
  • 原文地址:https://www.cnblogs.com/skynet/p/1708578.html
Copyright © 2011-2022 走看看