.NET委托解析

委托这个概念其实我们都很熟悉了,但是在使用的时候很多人还是无法去把控它,我们可以试想一下,在平时编码的时候,你是直接按照业务逻辑直接创建类,new出一个对象来进行操作的还是说有用到委托来更高效的完成一些功能.接下来博主将从委托最浅显的地方开始入手,中间插入对于委托源码的解析进行逐步加深巩固,简单来说,就是通过实例、概念、源码来最终通过本文的讲解能让我和阅读的您对于委托的理解提升一些.主题大概分为:

  • 通过实例了解委托的概念
  • 委托的回调
  • 委托的深入(委托链 - 合并删除委托)
  • 委托的源码解析
  • 泛型委托
  • 委托的进步(语法糖,协变和逆变,匿名,闭包)
  • 委托链、泛型委托源码解析
  • 委托和反射
  • 异步委托

【一】通过实例了解委托的概念

我们要学习委托,首要要了解委托的概念,什么是委托?C#中委托是如何定义的?这些基础性的知识了解之后我们在深入的去了解它, 在C#中,委托是一种类型,属于引用类型,委托的关键字是delegate,委托的定义和类的定义一样,所以凡是能定义类的地方也是可以定义委托的,public delegate void MyDelegate();这个定义了一个无返回值,无参的委托类型,那么下面我们来通过委托编写一段代码:

实例 1 : 委托的基本组成

 1 class Program
 2 {
 3     public delegate void MyDelegate();
 4     static void Main(string[] args)
 5     {
 6         MyDelegate myMessage = new MyDelegate(MyMethod);
 7         myMessage();
 8         Console.ReadLine();
 9     }
10     public static void MyMethod()
11     {
12         Console.WriteLine("我是通过委托调用的");
13     }
14 }

上述的代码是可以直接进行运行的,在上述代码中,首先我们声明了一个委托 MyDelegate, 它是无返回值,无参数的 ,同时我们还创建了一个方法MyMethod(), 这个方法也是 无返回值,无参数的。那么接下来我们在看一下主函数,在主函数中,我们创建了一个委托对象 myMessage  (委托是一种类型,属于引用类型), 然后在 new的时候我们可以看一下它要求的 "参数" 是什么. 如图  :

我们可以看到 在创建 MyDelegate 的对象时,要求传入一个 void() target  这个意思就是 无参,无返回值的一个目标函数 (这个我们后面还会用到,它的含义不仅仅如此),最后我们在调用这个委托对象(详情请看后面的源码解析).

【二】委托回调静态方法和实例方法

委托回调静态方法和实例方法的区别:

在实例 1 中,我们给委托传入的是一个静态的方法,在此顺便简单说一下静态方法和实例方法的区别 “静态方法都是通过关键字static来定义的,静态方法不需要实例这个对象就可以通过类名来访问这个对象。在静态方法中不能直接访问类中的非静态成员。而用实例方法则需要通过具体的实例对象来调用,并且可以访问实例对象中的任何成员”, 我们来通过一个实例来了解

 1 public delegate void MyPersonDelegate(string name);
 2 static void Main(string[] args)
 3 {
 4     MyPersonDelegate personDelegate = new MyPersonDelegate(Person.GetPersonName);
 5     personDelegate("Static");
 6     MyPersonDelegate personIntanceDelegate = new MyPersonDelegate(new PersonIntance().GetPersonName);
 7     personIntanceDelegate("Intance");
 8 }
 9 class Person
10 {
11     public static void GetPersonName(string age)
12     {
13         Console.WriteLine(age);
14     }
15 }
16 class PersonIntance
17 {
18     public void GetPersonName(string name)
19     {
20         Console.WriteLine(name);
21     }
22 }

在上述代码中,首先我们定义了一个委托MyPersonDelegate,它是无返回值,并且需要一个string类型的参数类型(在这里说一点,委托是可以进行协变和逆变的,具体请参考.NET可变性解析(协变和逆变)),然后我们分别定义了两个类personPersonInstance 其中Person中声明了一个GetPersonNam的静态方法,PersonIntance类中声明了一个GetPersonName的实例方法,在主函数Main中,我们分别进行调用.在执行的时候,我们会发现委托的实例后跟一个参数,这个参数其实就是方法的参数,因为我们所定义的委托要求的是一个执行一个无返回值,有一个string类型的参数的方法,在执行委托的时候,我故意多写了一个Invoke()这个方法,这里主要是可以先熟悉一下Invoke,因为接下来会涉及到它的一些知识点,Invoke也是调用委托的一种方法它和直接通过委托实例执行是一样的.那么对于回调静态方法和回调实例方法而言,委托的内部发生了什么?我们可以通过源码解析的方法来查看(在下面的段落描述).

【三】委托深入(委托链 - 合并删除委托)

在讨论委托链之前,我们先熟悉一下委托的合并和删除(这样可能更好理解一些),在Delegate类型下有两个静态的方法Combine和Remove (接下来的源码解析的会一一的讲解),Combine负责将两个委托实例的调用列表连接到一起,而Remove负责从一个委托实例中删除另一个实例的调用列表,下面我们通过一个实例来展示一下委托的合并和删除

实例 3 : 委托的合并和删除(Combine,Remove)

MyPersonDelegate personDelegate = new MyPersonDelegate(Person.GetPersonName); // 委托实例1

MyPersonDelegate personIntanceDelegate = new MyPersonDelegate(new PersonIntance().GetPersonName); // 委托实例2

var dele = (MyPersonDelegate)Delegate.Combine(personDelegate, personIntanceDelegate); // 通过Combine合并两个委托实例,得到一个新的委托实例

dele.Invoke("Albin"); // 输出合并之后的委托实例

Console.Readline();

在上述的代码中,首先我们定义了两个委托的实例 personIntanceDelegate  , personIntanceDelegate  接下来的一个段代码 我们看到 Delegate.Combine(),将这两个委托实例合并到了一起,然后输出,结果为 :

这就是将两个委托合并为了一个委托,并未我们在看一下更加简单的写法.

//var dele = (MyPersonDelegate)Delegate.Combine(personDelegate, personIntanceDelegate);
var dele = personDelegate += personIntanceDelegate;
dele.Invoke("Albin");

我们将Combine的方式改为+= 效果和Combine是一样的.(下面将有源码解析),熟悉事件的话,我们可以发现其实这个是事件加载是一样的.

委托的删除

在上面我们介绍了委托的合并,那么有合并就会有删除,在委托里有一个静态方法Remove,它用来将合并之后的委托进行移除,它要求的参数为 Delegate.Remove(source,value);这里指出要求一个委托的调用列表,以及提供委托移除source的调用列表,如图 :

实例 3 : 委托的Remove

    var deleRemove = (MyPersonDelegate)Delegate.Remove(personIntanceDelegate,dele);
    deleRemove.Invoke("Albin");

通过之前的Combine,这段代码并不难理解,这里就不多赘说了,接下来是它的简易写法

 var deleRemove = personIntanceDelegate -= dele;
 deleRemove.Invoke("ALbin");

最后两个的输出值都为 personIntanceDelegate的值

【四】委托的源码解析(反编译查看委托回调静态与实例的区别,以及委托链的本质)

接下来我们对前面所提到的委托回调和委托链进行反编译,查看委托在调用的时候内部是如何实行的,先贴出委托的部分源码 :

 Delegate部分源码

在上述的源码中,我们看到Delegate有四个私有字段,分别为:object _target;object _methodBase;IntPtr _methodPtr;IntPtr _methodPtrAux;

_target: 返回的是一个引用类型,它是用来表示引用(回调)的方法如果是静态的方法 _target返回Null,如果回调的是实例对象则返回该方法的引用地址,在往下看有一个Target的属性,这个属性对应的就是_target这个字段,另外Target返回的是this.GetTarget(),通过注释 " The object on which the current delegate invokes the instance method, if the delegate represents an instance method; null if the delegate represents a static method. "也证实了_target的作用.

internal virtual object GetTarget()
{
    if (!this._methodPtrAux.IsNull())
    {
        return null;
    }
    return this._target;
}

我们查看GetTarget这个属性之后,发现这里面有一个字段  _methodPtrAux ,同时我们在看一下  MethodInfo GetMethodImpl() 这个方法描述为 :The caller does not have access to the method represented by the delegate (for example, if the method is private).  如果委托指向的是实例方法,则_methodPtrAux就是0 ,如果委托指向的是静态方法,则这时_methodPtrAux起的作用与_mthodPtr在委托指向实例方法的时候是一样的.

_methodPtr 是指向该方法的指针.

_methodBase 是给委托赋值时传递的方法

【五】泛型委托

泛型委托主要为我们解决定义委托的数量比较多,在.NET FreamWork 支持泛型之后,我们就可以用泛型的方式来定义委托,首先泛型的好处其中之一就是减少复杂性,提高可重用性(详细请参考.NET泛型解析(上)),下面我们通过实例来了解一下泛型委托的魅力.

实例 4 : 泛型委托之Action

    // Action实例
    Action<string> action = new Action<string>(Person.GetPersonName);
    action.Invoke("Albin");
    Console.ReadLine();

在上述代码中,我们创建了一个泛型委托 action, 并且我们在创建的时候可以看到Action<> 它要求的是一个委托的参数类型,并且在创建实例的时候和我们实例1一样要求一个void (string)Target, 无返回值,有一个参数. 并且参数类型指定为了 in,说明它是可以逆变的(.NET可变性解析(协变和逆变));

.NET FreamWork为我们提供了17个Action委托,它们从无参数到最多16个参数,这个完全够我们用了(除非你的委托要传16以上的参数,那么只有自己定义了) , 其中注意一点 : Action给我们提供的是只有参数而不带返回值的委托,那么如果我们要传递带有返回值和参数的呢? 这时,.NET FreamWork也考虑到了这一点,它为我们提供了另外一个函数 Func,它和Action一样提供了17个参数另加一个返回值类型,当第一次使用它们的时候,感觉整天天空都是蓝蓝的...简直太帅了.

下面我们通过一个实例来了解一下Func函数

实例 5 : 泛型委托之Func

    // Func 实例
    Func<string, string> func = new Func<string, string>(Person.GetName);
    var result = func.Invoke("This is Arg");
    Console.WriteLine(result);
    Console.ReadLine();

class Person
{
    public static string GetName(string name)
    {
        return name;
    }
}

在上述的代码中,我们创建了一个Func的实例,要求func所要回调的方法有一个string类型的返回值,并且有一个string类型的参数,所以我们在Person类中定义了一个 GetName的方法,在func.Invoke(""),调用的时候,它所返回值的类型为GetName所返回的类型.最后输出结果为 :

泛型委托的好处:

在平时的开发过程中,我们应尽量使用泛型委托的方式来使用委托,避免使用自定义的委托

第一 : 可以减少我们委托的定义数量

第二 : 泛型是类型安全的

第三 : 方便进行协变和逆变

第四 : 简化代码

【六】委托的进步(语法糖,协变和逆变,匿名,闭包)

C#语法糖 : 所谓语法糖是在C#代码中,简化代码量、是代码编写的更加优美,所以称之为语法糖.

匿名函数  : 在C#2.0中引入的匿名函数,所谓匿名函数就是没有实际方法声明的委托实例,它们是直接内嵌在代码中的

Lambda  :  在C#3.0中引入的Lambda表达式,它比匿名方法更加的简洁

在这里不会过深的去描述Lambda和匿名这一块,因为过几天会编写关于 《.NET解析之Lambda和匿名的内部机制实现》 方面的文章.在这里我们只需要知道就可以了.

实例 6 : 通过Lambda , 匿名方法类简化委托的代码.

MyPersonDelegate personDelegate = p => Console.WriteLine(p.ToString());
personDelegate.Invoke("无返回值,有参数");
MyDelegate myDelegate = () => Console.WriteLine("无参,无返回值");
myDelegate();
MyPersonDelegateStr delegateStr = p => { return p; };
Console.WriteLine(delegateStr.Invoke("有参数,有返回值"));
Console.ReadLine();

实例 7: 通过闭包实现

    var f = Func();
    Console.WriteLine(f());
    Console.ReadLine();
    public static Func<int> Func()
    {
        var i = 10;
        return () =>
            {
                return i;
            };
    }

上述的代码我们可以反编译看一下 :

可以看出来return返回的是一个匿名委托,因为Func它是要求必须有一个返回值的,从中返回的一个匿名的委托对象,在匿名委托中,我加了一个Console.WriteLine(i); 在实例的中的代码中是没有的, 这一点主要是因为 能体现出一个方法体来,如果按照我们实例的写法反编译出来直接就是 return () => i; 闭包本身就不好理解, 这个可以专门拿出一个文章来讲解它.在这里就不深究了.

【七】委托链,泛型委托源码解析

委托链/多播委托/合并删除委托源码解析

[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
        public static Delegate Combine(Delegate a, Delegate b)
        {
            if (a == null)
            {
                return b;
            }
            return a.CombineImpl(b);
        }

上述代码为 Combine的内部实现,我们可以看到a为null则引用了一个空的方法实例,直接返回另一个委托对象,通过CombineImpl来串联两个委托的调用列表

删除委托

/// <summary>Removes the last occurrence of the invocation list of a delegate from the invocation list of another delegate.</summary>
        /// <returns>A new delegate with an invocation list formed by taking the invocation list of <paramref name="source" /> and removing the last occurrence of the invocation list of <paramref name="value" />, if the invocation list of <paramref name="value" /> is found within the invocation list of <paramref name="source" />. Returns <paramref name="source" /> if <paramref name="value" /> is null or if the invocation list of <paramref name="value" /> is not found within the invocation list of <paramref name="source" />. Returns a null reference if the invocation list of <paramref name="value" /> is equal to the invocation list of <paramref name="source" /> or if <paramref name="source" /> is a null reference.</returns>
        /// <param name="source">The delegate from which to remove the invocation list of <paramref name="value" />. </param>
        /// <param name="value">The delegate that supplies the invocation list to remove from the invocation list of <paramref name="source" />. </param>
        /// <exception cref="T:System.MemberAccessException">The caller does not have access to the method represented by the delegate (for example, if the method is private). </exception>
        /// <exception cref="T:System.ArgumentException">The delegate types do not match.</exception>
        /// <filterpriority>1</filterpriority>
        [__DynamicallyInvokable, SecuritySafeCritical]
        public static Delegate Remove(Delegate source, Delegate value)
        {
            if (source == null)
            {
                return null;
            }
            if (value == null)
            {
                return source;
            }
            if (!Delegate.InternalEqualTypes(source, value))
            {
                throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTypeMis"));
            }
            return source.RemoveImpl(value);
        }

上述代码为Remove的内部实现,从另一个委托的调用列表中移除委托的调用列表的最后一个匹配的项,通过RemoveImpl方法移除,RemoveImpl方法内部实现:

/// <summary>Removes the invocation list of a delegate from the invocation list of another delegate.</summary>
/// <returns>A new delegate with an invocation list formed by taking the invocation list of the current delegate and removing the invocation list of <paramref name="value" />, if the invocation list of <paramref name="value" /> is found within the current delegate‘s invocation list. Returns the current delegate if <paramref name="value" /> is null or if the invocation list of <paramref name="value" /> is not found within the current delegate‘s invocation list. Returns null if the invocation list of <paramref name="value" /> is equal to the current delegate‘s invocation list.</returns>
/// <param name="d">The delegate that supplies the invocation list to remove from the invocation list of the current delegate. </param>
/// <exception cref="T:System.MemberAccessException">The caller does not have access to the method represented by the delegate (for example, if the method is private). </exception>
protected virtual Delegate RemoveImpl(Delegate d)
{
    if (!d.Equals(this))
    {
        return this;
    }
    return null;
}

时间: 2024-10-14 01:07:07

.NET委托解析的相关文章

.NET委托解析(异步委托)

上一篇我们了解到了,委托的基本感念,列举了几个委托的实例,并根据实例来反编译源码查看.NET 委托的内部实现,从浅入深的角度来详细的去解析委托的实质,本文将系上篇继续讨论异步委托的实现以及异步委托的源码解析. 首先本文只会从委托的层面的去编写,不会涉及到深层次的异步.(后续的系列中将会对异步进行深入讲解.敬请关注.). 委托的异步调用方式 在上一篇中我们在实例中是直接通过委托对象的,例如: private static void Main(string[] args)  {        Pro

.NET解析专题系列

鉴于在每个程序员在进阶的路上都要进行一些语言层面的基础回顾总结,编写本系列一是为了巩固自己的知识点,二是希望能帮助一些.NET程序员在进阶路上少走一些弯路,三是希望各位也能通过本系列多多交流. 这一系列的文章,不出意外的话,楼主会两天或者三天更新一篇,这也是为了更好更全面的总结到每个知识点,同时楼主也是每天在工作之余抽出四个小时时间进行学习,总结,编写博客.目的是达到最终的质量达到最优. [1].NET泛型解析(上) [2].NET泛型解析(下) [3].NET可变性解析(协变和逆变) [4].

.NET解析专题目录

[1].NET泛型解析(上) [2].NET泛型解析(下) [3].NET可变性解析(协变和逆变) [4].NET委托解析 [5].NET解析(异步委托) [6].NET解析(反射) [7].NET解析之线程 [8].NET解析(面向接口编程) [9].NET解析之CLR内存处理机制 [10].NET解析之CLR垃圾回收机制 [11].NET解析之异常处理机制 [12].NET解析之CLR寄宿 [13].NET解析之序列化 [14].NET解析之异步/并发编程

javascript事件委托与&quot;坑&quot;

问题 这是在工作中遇到的一个问题: 一个textarea文本框,需要动态监听输入文本个数 方案 通过谷歌查到一种完美的兼容方法 "如果使用 onkeydown.onkeypress.onkeyup 这个几个键盘事件来监测的话,监听不了右键的复制.剪贴和粘贴这些操作,处理组合快捷键也很麻烦 因此这篇文章向大家介绍一种完美的解决方案:结合 HTML5 标准事件 oninput 和 IE 专属事件 onpropertychange 事件来监听输入框值变化." 引用自 http://www.c

ClassPathXMLApplicationContext上下文加载过程

今天看了一下<spring技术内幕>,看了下spring IOC容器的加载过程,但是里面的代码很杂,就自己用源码的测试用例debug了一下看了下过程 测试用例 @Test public void testSingleConfigLocation() throws IOException { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(FQ_SIMPLE_CONTEXT); assertTrue(

委托IL解析-----封装逻辑和代码复用

委托IL解析-----封装逻辑和代码复用 1.委托的本质 委托大家都不陌生吧,我们经常都会接触到或用到.LINQ查询就是基于委托来实现的. 我们常常这样定义委托: public delegate void SayHiDelegate(string name); 那么委托的本质是什么呢? 在介绍委托的本质前,我们先来认识一下IL,IL是中间语言,是我们在VS写的代码和计算机二进制代码的中间语言.我们的代码编译生成IL,IL通过GIT最终编译成计算机能识别的计算机二进制代码.IL可以看到真正原生的C

IOS开发之----协议与委托(Protocol and Delegate) 实例解析

1 协议: 协议,类似于Java或C#语言中的接口,它限制了实现类必须拥有哪些方法. 它是对对象行为的定义,也是对功能的规范. 在写示例之前我给大家说下@required和@optional这两个关键字 他们两个是在声明协议的时候用到,@required是必须实现的方法,要不会报黄色警告[email protected]是可选实现!实现还是不实现都不会报警告! 示例: 1 2 3 4 5 6 7 8 9 // GoodChild.h #import @protocol GoodChild -(v

委托、事件、Observer观察者模式的使用解析一

一.前言 委托.事件得理论我就不解释了,不会的时候觉得很难,会了发现挺简单的,回头想想其实在JavaScript中常常用到,譬如:setTimeout()就是典型的委托. 二.传统编码方式 传统的调用方式如下,如果新加语言方法需要修改SayHello方法,增加case很不方便扩展 /// <summary> /// 普通调用方式 /// </summary> public class TestOld { public void English(string name) { Cons

C#委托全解析

1.委托的基本使用,类似于函数指针,但比函数指针更安全,功能更强大.初始化有两种方式,可注册静态函数,也可以注册成员函数,委托的调用也有两种方式. 2.委托做为函数参数. 3.Action委托 4.Func委托 其实都是为了简化代码,使用更少的代码达到相同的效果,不需要我们显示的声明一个委托 5.多播委托 6.匿名方法 7.lambda 8.事件