.NET委托解析

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

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

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

咱们要学习委托,首要要了解委托的概念,什么是委托?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)ide

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是同样的.(下面将有源码解析),熟悉事件的话,咱们能够发现其实这个是事件加载是同样的.ui

委托的删除this

在上面咱们介绍了委托的合并,那么有合并就会有删除,在委托里有一个静态方法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的值

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

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

 1 public abstract class Delegate : ICloneable, ISerializable  2  {  3  [ForceTokenStabilization, SecurityCritical]  4         internal object _target;  5  [SecurityCritical]  6         internal object _methodBase;  7  [ForceTokenStabilization, SecurityCritical]  8         internal IntPtr _methodPtr;  9  [ForceTokenStabilization, SecurityCritical] 10         internal IntPtr _methodPtrAux; 11         /// <summary>Gets the method represented by the delegate.</summary>
12         /// <returns>A <see cref="T:System.Reflection.MethodInfo" /> describing the method represented by the delegate.</returns>
13         /// <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>
14         /// <filterpriority>2</filterpriority>
15  [__DynamicallyInvokable] 16         public MethodInfo Method 17  { 18             [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] 19             get
20  { 21                 return this.GetMethodImpl(); 22  } 23  } 24         /// <summary>Gets the class instance on which the current delegate invokes the instance method.</summary>
25         /// <returns>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.</returns>
26         /// <filterpriority>2</filterpriority>
27  [__DynamicallyInvokable] 28         public object Target 29  { 30             [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] 31             get
32  { 33                 return this.GetTarget(); 34  } 35  } 36         /// <summary>Initializes a delegate that invokes the specified instance method on the specified class instance.</summary>
37         /// <param name="target">The class instance on which the delegate invokes <paramref name="method" />. </param>
38         /// <param name="method">The name of the instance method that the delegate represents. </param>
39         /// <exception cref="T:System.ArgumentNullException">
40         ///   <paramref name="target" /> is null.-or- <paramref name="method" /> is null. </exception>
41         /// <exception cref="T:System.ArgumentException">There was an error binding to the target method.</exception>
42  [SecuritySafeCritical] 43         protected Delegate(object target, string method) 44  { 45             if (target == null) 46  { 47                 throw new ArgumentNullException("target"); 48  } 49             if (method == null) 50  { 51                 throw new ArgumentNullException("method"); 52  } 53             if (!this.BindToMethodName(target, (RuntimeType)target.GetType(), method, (DelegateBindingFlags)10)) 54  { 55                 throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTargMeth")); 56  } 57  } 58         /// <summary>Initializes a delegate that invokes the specified static method from the specified class.</summary>
59         /// <param name="target">The <see cref="T:System.Type" /> representing the class that defines <paramref name="method" />. </param>
60         /// <param name="method">The name of the static method that the delegate represents. </param>
61         /// <exception cref="T:System.ArgumentNullException">
62         ///   <paramref name="target" /> is null.-or- <paramref name="method" /> is null. </exception>
63         /// <exception cref="T:System.ArgumentException">
64         ///   <paramref name="target" /> is not a RuntimeType. See Runtime Types in Reflection.-or-<paramref name="target" /> represents an open generic type.</exception>
65  [SecuritySafeCritical] 66         protected Delegate(Type target, string method) 67  { 68             if (target == null) 69  { 70                 throw new ArgumentNullException("target"); 71  } 72             if (target.IsGenericType && target.ContainsGenericParameters) 73  { 74                 throw new ArgumentException(Environment.GetResourceString("Arg_UnboundGenParam"), "target"); 75  } 76             if (method == null) 77  { 78                 throw new ArgumentNullException("method"); 79  } 80             RuntimeType runtimeType = target as RuntimeType; 81             if (runtimeType == null) 82  { 83                 throw new ArgumentException(Environment.GetResourceString("Argument_MustBeRuntimeType"), "target"); 84  } 85             this.BindToMethodName(null, runtimeType, method, (DelegateBindingFlags)37); 86         }
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;
}
source.RemoveImpl(value); source将从中移除 value 的调用列表, value提供将从其中移除 source 的调用列表的调用列表.
一个新委托,其调用列表的构成方法为:获取 source 的调用列表,若是在 source 的调用列表中找到了 value 的调用列表,则从中移除 value 的最后一个调用列表.
 若是 value 为 null,或在 source 的调用列表中没有找到 value 的调用列表,则返回 source.若是 value 的调用列表等于 source 的调用列表,或 source 为空引用,则返回空引用.
例如 : 在咱们的实例 3中若是将 var deleRemove = (MyPersonDelegate)Delegate.Remove(personIntanceDelegate,dele);将source的值改成dele,将value的值改成personIntanceDelegate,则会返回一个Null.
泛型委托源码解析
首先咱们来看一下Action的委托:

在这里咱们就拿出一些Action中有8个参数的来举例了,注释中标明 : Encapsulates a method that has eight parameters and does not return a value. 意思 : 封装另外一个方法,具备八个参数而且不返回值的方法,在来看一下Action的定义,Action被定义为delegate类型void返回的方法,而且有1-18个指定为in的参数,咱们说过了in能够进行逆变.

而后咱们在来看Func

由于Func的参数也较多,咱们这里只拿出带有8个参数的来举例了,从注释中咱们知道 : Encapsulates a method that has eight parameters and returns a value of the type specified by the ,封装一个方法,具备八个参数并返回一个值所指定的方法,同时,返回值为out,能够进行协变.

由于这篇文章篇幅已经较长,对于异步委托在下篇文章中进行解析.委托和反射留在反射的文章中进行解析.另外但愿本文可以帮助到你了解到更多或者更深.

若是你以为本文对你有帮助的话,请点右下角的推荐,或者直接关注我,后续将不断更新.NET解析这一系列的文章....

.NET解析系列目录 

【刘彬版权全部,如转载请注明出处.】

相关文章
相关标签/搜索