.NET委托解析

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

.NET委托解析
委托这个概念其实我们都很熟悉了,但是在使⽤的时候很多⼈还是⽆法去把控它,我们可以试想⼀下,在平时编码的时候,你是直接按照业务逻辑直接创建类,new出⼀个对象来进⾏操作的还是说有⽤到委托来更⾼效的完成⼀些功能.接下来博主将从委托最浅显的地⽅开始⼊⼿,中间插⼊对于委托源码的解析进⾏逐步加深巩固,简单来说,就是通过实例、概念、源码来最终通过本⽂的讲解能让我和阅读的您对于委托的理解提升⼀些.主题⼤概分为:
通过实例了解委托的概念
委托的回调
委托的深⼊(委托链 - 合并删除委托)
委托的源码解析
泛型委托
委托的进步(语法糖,协变和逆变,匿名,闭包)
委托链、泛型委托源码解析
委托和反射
异步委托
【⼀】通过实例了解委托的概念
我们要学习委托,⾸要要了解委托的概念,什么是委托?C#中委托是如何定义的?这些基础性的知识了解之后我们在深⼊的去了解它, 在C#中,委托是⼀种类型,属于引⽤类型,委托的关键字是delegate,委托的定义和类的定义⼀样,所以凡是能定义类的地⽅也是可以定义委托的,public delegate void MyDelegate();这个定义了⼀个⽆返回值,⽆参的委托类型,那么下⾯我们来通过委托编写⼀段代码:
实例 1 :委托的基本组成
1class Program
2 {
3public delegate void MyDelegate();
4static void Main(string[] args)
5 {
6 MyDelegate myMessage = new MyDelegate(MyMethod);
7 myMessage();
8 Console.ReadLine();
9 }
10public static void MyMethod()
11 {
12 Console.WriteLine("我是通过委托调⽤的");
13 }
14 }
上述的代码是可以直接进⾏运⾏的,在上述代码中,⾸先我们声明了⼀个委托MyDelegate,它是⽆返回值,⽆参数的,同时我们还创建了⼀个⽅法MyMethod(), 这个⽅法也是⽆返回值,⽆参数的。

那么接下来我们在看⼀下主函数,在主函数中,我们创建了⼀个委托对象myMessage (委托是⼀种类型,属于引⽤类型), 然后在 new的时候我们可以看⼀下它要求的 "参数" 是什么.如图:
我们可以看到在创建 MyDelegate 的对象时,要求传⼊⼀个void() target这个意思就是⽆参,⽆返回值的⼀个⽬标函数(这个我们后⾯还会⽤到,它的含义不仅仅如此),最后我们在调⽤这个委托对象(详情请看后⾯的源码解析).
【⼆】委托回调静态⽅法和实例⽅法
委托回调静态⽅法和实例⽅法的区别:
在实例 1 中,我们给委托传⼊的是⼀个静态的⽅法,在此顺便简单说⼀下静态⽅法和实例⽅法的区别 “静态⽅法都是通过关键字static来定义的,静态⽅法不需要实例这个对象就可以通过类名来访问这个对象。

在静态⽅法中不能直接访问类中的⾮静态成员。

⽽⽤实例⽅法则需要通过具体的实例对象来调⽤,并且可以访问实例对象中的任何成员”,我们来通过⼀个实例来了解
1public delegate void MyPersonDelegate(string name);
2static 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 }
9class Person
10 {
11public static void GetPersonName(string age)
12 {
13 Console.WriteLine(age);
14 }
15 }
16class PersonIntance
17 {
18public void GetPersonName(string name)
19 {
20 Console.WriteLine(name);
21 }
22 }
在上述代码中,⾸先我们定义了⼀个委托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)bine(personDelegate, personIntanceDelegate); // 通过Combine合并两个委托实例,得到⼀个新的委托实例
dele.Invoke("Albin"); // 输出合并之后的委托实例
Console.Readline();
在上述的代码中,⾸先我们定义了两个委托的实例personIntanceDelegate , personIntanceDelegate 接下来的⼀个段代码我们看到 bine(),将这两个委托实例合并到了⼀起,然后输出,结果为 :
这就是将两个委托合并为了⼀个委托,并未我们在看⼀下更加简单的写法.
//var dele = (MyPersonDelegate)bine(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的值
【四】委托的源码解析(反编译查看委托回调静态与实例的区别,以及委托链的本质)
接下来我们对前⾯所提到的委托回调和委托链进⾏反编译,查看委托在调⽤的时候内部是如何实⾏的,先贴出委托的部分源码 :
1public abstract class Delegate : ICloneable, ISerializable
2 {
3 [ForceTokenStabilization, SecurityCritical]
4internal object _target;
5 [SecurityCritical]
6internal object _methodBase;
7 [ForceTokenStabilization, SecurityCritical]
8internal IntPtr _methodPtr;
9 [ForceTokenStabilization, SecurityCritical]
10internal 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]
16public MethodInfo Method
17 {
18 [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
19get
20 {
21return 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]
28public object Target
29 {
30 [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
31get
32 {
33return 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]
43protected Delegate(object target, string method)
44 {
45if (target == null)
46 {
47throw new ArgumentNullException("target");
48 }
49if (method == null)
50 {
51throw new ArgumentNullException("method");
52 }
53if (!this.BindToMethodName(target, (RuntimeType)target.GetType(), method, (DelegateBindingFlags)10))
54 {
55throw 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]
66protected Delegate(Type target, string method)
67 {
68if (target == null)
69 {
70throw new ArgumentNullException("target");
71 }
72if (target.IsGenericType && target.ContainsGenericParameters)
73 {
74throw new ArgumentException(Environment.GetResourceString("Arg_UnboundGenParam"), "target");
75 }
76if (method == null)
77 {
78throw new ArgumentNullException("method");
79 }
80 RuntimeType runtimeType = target as RuntimeType;
81if (runtimeType == null)
82 {
83throw new ArgumentException(Environment.GetResourceString("Argument_MustBeRuntimeType"), "target");
84 }
85this.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 ⽀持泛型之后,我们就可以⽤泛型的⽅式来定义委托,⾸先泛型的好处其中之⼀就是减少复杂性,提⾼可重⽤性(详细请参考),下⾯我们通过实例来了解⼀下泛型委托的魅⼒.
实例 4 : 泛型委托之Action
// Action实例
Action<string> action = new Action<string>(Person.GetPersonName);
action.Invoke("Albin");
Console.ReadLine();
在上述代码中,我们创建了⼀个泛型委托 action, 并且我们在创建的时候可以看到Action<> 它要求的是⼀个委托的参数类型,并且在创建实例的时候和我们实例1⼀样要求⼀个void
(string)Target, ⽆返回值,有⼀个参数. 并且参数类型指定为了 in,说明它是可以逆变的();
.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 bineImpl(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 invo /// <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 /// <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解析这⼀系列的⽂章....
【刘彬版权所有,如转载请注明出处.】。

相关文档
最新文档