.NET委托解析
委托这个概念其实我们都很熟悉了,但是在使用的时候很多人还是无法去把控它,我们可以试想一下,在平时编码的时候,你是直接按照业务逻辑直接创建类,new出一个对象来进行操作的还是说有用到委托来更高效的完成一些功能.接下来博主将从委托最浅显的地方开始入手,中间插入对于委托源码的解析进行逐步加深巩固,简单来说,就是通过实例、概念、源码来最终通过本文的讲解能让我和阅读的您对于委托的理解提升一些.主题大概分为:
- 通过实例了解委托的概念
- 委托的回调
- 委托的深入(委托链 - 合并删除委托)
- 委托的源码解析
- 泛型委托
- 委托的进步(语法糖,协变和逆变,匿名,闭包)
- 委托链、泛型委托源码解析
- 委托和反射
- 异步委托
【一】通过实例了解委托的概念
class Program
{
public delegate void MyDelegate();
static void Main(string[] args)
{
MyDelegate myMessage = new MyDelegate(MyMethod);
myMessage();
Console.ReadLine();
}
public static void MyMethod()
{
Console.WriteLine("我是通过委托调用的");
}
}
上述的代码是可以直接进行运行的,在上述代码中,首先我们声明了一个委托 MyDelegate, 它是无返回值,无参数的 ,同时我们还创建了一个方法MyMethod(), 这个方法也是 无返回值,无参数的。那么接下来我们在看一下主函数,在主函数中,我们创建了一个委托对象 myMessage (委托是一种类型,属于引用类型), 然后在 new的时候我们可以看一下它要求的 "参数" 是什么. 如图 :
【二】委托回调静态方法和实例方法
public delegate void MyPersonDelegate(string name);
static void Main(string[] args)
{
MyPersonDelegate personDelegate = new MyPersonDelegate(Person.GetPersonName);
personDelegate("Static");
MyPersonDelegate personIntanceDelegate = new MyPersonDelegate(new PersonIntance().GetPersonName);
personIntanceDelegate("Intance");
}
class Person
{
public static void GetPersonName(string age)
{
Console.WriteLine(age);
}
}
class PersonIntance
{
public void GetPersonName(string name)
{
Console.WriteLine(name);
}
}
在上述代码中,首先我们定义了一个委托MyPersonDelegate,它是无返回值,并且需要一个string类型的参数类型(在这里说一点,委托是可以进行协变和逆变的,具体请参考.NET可变性解析(协变和逆变)),然后我们分别定义了两个类person和PersonInstance 其中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的值
【四】委托的源码解析(反编译查看委托回调静态与实例的区别,以及委托链的本质)
接下来我们对前面所提到的委托回调和委托链进行反编译,查看委托在调用的时候内部是如何实行的,先贴出委托的部分源码 :
public abstract class Delegate : ICloneable, ISerializable
{
[ForceTokenStabilization, SecurityCritical]
internal object _target;
[SecurityCritical]
internal object _methodBase;
[ForceTokenStabilization, SecurityCritical]
internal IntPtr _methodPtr;
[ForceTokenStabilization, SecurityCritical]
internal IntPtr _methodPtrAux;
/// <summary>Gets the method represented by the delegate.</summary>
/// <returns>A <see cref="T:System.Reflection.MethodInfo" /> describing the method represented by the delegate.</returns>
/// <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>
/// <filterpriority></filterpriority>
[__DynamicallyInvokable]
public MethodInfo Method
{
[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
get
{
return this.GetMethodImpl();
}
}
/// <summary>Gets the class instance on which the current delegate invokes the instance method.</summary>
/// <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>
/// <filterpriority></filterpriority>
[__DynamicallyInvokable]
public object Target
{
[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
get
{
return this.GetTarget();
}
}
/// <summary>Initializes a delegate that invokes the specified instance method on the specified class instance.</summary>
/// <param name="target">The class instance on which the delegate invokes <paramref name="method" />. </param>
/// <param name="method">The name of the instance method that the delegate represents. </param>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="target" /> is null.-or- <paramref name="method" /> is null. </exception>
/// <exception cref="T:System.ArgumentException">There was an error binding to the target method.</exception>
[SecuritySafeCritical]
protected Delegate(object target, string method)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
if (method == null)
{
throw new ArgumentNullException("method");
}
if (!this.BindToMethodName(target, (RuntimeType)target.GetType(), method, (DelegateBindingFlags)))
{
throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTargMeth"));
}
}
/// <summary>Initializes a delegate that invokes the specified static method from the specified class.</summary>
/// <param name="target">The <see cref="T:System.Type" /> representing the class that defines <paramref name="method" />. </param>
/// <param name="method">The name of the static method that the delegate represents. </param>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="target" /> is null.-or- <paramref name="method" /> is null. </exception>
/// <exception cref="T:System.ArgumentException">
/// <paramref name="target" /> is not a RuntimeType. See Runtime Types in Reflection.-or-<paramref name="target" /> represents an open generic type.</exception>
[SecuritySafeCritical]
protected Delegate(Type target, string method)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
if (target.IsGenericType && target.ContainsGenericParameters)
{
throw new ArgumentException(Environment.GetResourceString("Arg_UnboundGenParam"), "target");
}
if (method == null)
{
throw new ArgumentNullException("method");
}
RuntimeType runtimeType = target as RuntimeType;
if (runtimeType == null)
{
throw new ArgumentException(Environment.GetResourceString("Argument_MustBeRuntimeType"), "target");
}
this.BindToMethodName(null, runtimeType, method, (DelegateBindingFlags));
}
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 = ;
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委托解析的更多相关文章
- .NET委托解析(异步委托)
上一篇我们了解到了,委托的基本感念,列举了几个委托的实例,并根据实例来反编译源码查看.NET 委托的内部实现,从浅入深的角度来详细的去解析委托的实质,本文将系上篇继续讨论异步委托的实现以及异步委托的源 ...
- javascript事件委托与"坑"
问题 这是在工作中遇到的一个问题: 一个textarea文本框,需要动态监听输入文本个数 方案 通过谷歌查到一种完美的兼容方法 "如果使用 onkeydown.onkeypress.onke ...
- Spring源码系列 — BeanDefinition
一.前言 回顾 在Spring源码系列第二篇中介绍了Environment组件,后续又介绍Spring中Resource的抽象,但是对于上下文的启动过程详解并未继续.经过一个星期的准备,梳理了Spri ...
- .NET面试题解析(05)-常量、字段、属性、特性与委托
系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 弱小和无知不是生存的障碍,傲慢才是!——<三体> 常见面试题目: 1. const和reado ...
- dotnet 委托的实现解析
缘起 最近被问到什么是.Net中的委托.问题虽然简单却无从回答.只能说委托是托管世界的函数指针,这么说没啥大毛病,但也都是毛病(当时自己也知道这么说不太对,不过自己不太爱用这个也没准备确实没有更好的答 ...
- C#委托全解析
什么是委托? 委托类似于C语 ...
- C#中委托和事件的区别实例解析
这篇文章主要介绍了C#中委托和事件的区别,并分别以实例形式展示了通过委托执行方法与通过事件执行方法,以及相关的执行流程与原理分析,需要的朋友可以参考下 本文实例分析了C#中委托和事件的区别,分享给大家 ...
- [C# 基础知识系列]专题一:深入解析委托——C#中为什么要引入委托
转自http://www.cnblogs.com/zhili/archive/2012/10/22/Delegate.html 引言: 对于一些刚接触C# 不久的朋友可能会对C#中一些基本特性理解的不 ...
- IOS开发之----协议与委托(Protocol and Delegate) 实例解析
1 协议: 协议,类似于Java或C#语言中的接口,它限制了实现类必须拥有哪些方法. 它是对对象行为的定义,也是对功能的规范. 在写示例之前我给大家说下@required和@optional这两个关键 ...
随机推荐
- 史上最详细的Android Studio系列教程四--Gradle基础
https://segmentfault.com/a/1190000002439306#articleHeader0
- i++和++i
这个问题总是讨论,有时又被弄晕了,特来复习一下 ; ; cout<<s<<endl; cout<<5,而i+++4返回4,其实这样的i++先运算,再加,++i先加再 ...
- 初学JDBC,JDBC工具类的简单封装
//工具类不需要被继承 public final class JdbcUtils{ //封装数据库连接参数,便于后期更改参数值 private static String url="jdbc ...
- mysql大表如何优化
作者:哈哈链接:http://www.zhihu.com/question/19719997/answer/81930332来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处 ...
- 如何使用Unix/Linux grep命令——磨刀不误砍柴工系列
http://man.linuxde.net/grep ---------------------------------------------------- 如何使用Unix/Linux gre ...
- linux kill信号列表
linux kill信号列表 $ kill -l1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL5) SIGTRAP 6) ...
- 9个 SSH常用命令选项
9个 SSH常用命令选项 SSH 是什么 SSH(全称 Secure Shell)是一种加密的网络协议.使用该协议的数据将被加密,如果在传输中间数据泄漏,也可以确保没有人能读取出有用信息.要使用 SS ...
- MapReduce使用JobControl管理实例
import java.io.IOException; import java.util.StringTokenizer; import org.apache.hadoop.fs.Path; impo ...
- [NOIP2015]推销员
[NOIP2015]推销员 试题描述 阿明是一名推销员,他奉命到螺丝街推销他们公司的产品.螺丝街是一条死胡同,出口与入口是同一个,街道的一侧是围墙,另一侧是住户.螺丝街一共有 N 家住户,第 i 家住 ...
- CentOS6 下安装HP-LaserJet 1020打印机
因为实验室有个多余的老服务器,所以近段时间想把老服务器做成打印机服务器,同时因为最近在学习linux,所以就像在CentOS6.3 上安装打印机驱动.因为是新手,所以走了不少弯路,今天终于把打印机安装 ...