.Neter所应该彻底了解的委托
本⽂将通过引出⼏个问题来,并且通过例⼦来剖析C#中的委托以及⽤法,做抛砖引⽟的作⽤
对于委托我发现⼤部分⼈都有以下问题,或者可能在⾯试中遇过这样的:
委托是不是相当于C/C++的函数指针?
委托究竟是什么?
委托究竟是⽤来⼲嘛的?
委托跟匿名函数的区别?
委托与事件的关系?
我们先来声明和使⽤C++的函数指针:
代码如下:
#include <iostream>
using namespace std;
typedef int (*Foohandle)(int a,int b);
int fooMenthod(int a, int (*foohandle1)(int a,int b)) //回调函数
{
return a + (*foohandle1)(2,3);//也可以写成foohandle1(2,3)
}
int add(int a,int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
int main()
{
Foohandle foohandle = add;
int (*foohandle1)(int a, int b) = &add;
cout << foohandle(2,3)<<endl;
cout << foohandle1(2,3) << endl;
cout << typeid(Foohandle).name() << endl;
cout << typeid(foohandle).name()<<endl;
cout << typeid(foohandle1).name() << endl;
cout << fooMenthod(2, add)<<endl;
cout << fooMenthod(2, multiply);
}
输出结果如下:
在代码中,我声明定义了两个函数add和multiply,然后⽤typedef⽅式声明了函数指针,接着我分别将add赋值给Foohandle这种函数指针类型的foohandle变量,然后⽤&add这种解地址的⽅式赋值给⼀个返回值为int,且带有两个参数的函数指针foohandle1,其中(*foohandle1)是函数名,最后我输出发现它们类型和输出都是⼀致的,再后⾯,我们定义了⼀个fooMenthod函数,返回值是int,且其中⼀个参数是函数指针,那么我再最后调⽤两次,分别将add和multiply函数,赋值给它,这时候add和multiply就是fooMenthod函数的回调函数,且此时输出结果会被两个函数内部不同实现所影响
那么我们可以做个总结:
⾸先函数指针就是⼀个内存地址,指向函数的⼊⼝内存地址
当函数指针做⼀个函数的参数时,确实会起到⼀定解耦作⽤
函数指针很明显是类型不安全的
我们再来声明和使⽤委托:
public delegate int Foohandle(int a, int b);
class Program
{
static void Main(string[] args)
{
Foohandle foohandle = new Foohandle(add);
Console.WriteLine(foohandle(2, 3));
Console.WriteLine(foohandle.GetType().Name);
Console.WriteLine(fooMenthod(2, add));
Console.WriteLine(fooMenthod(2, multiply));
Console.WriteLine($"foohandle所调⽤函数函数名:{foohandle.Method.Name}");
Console.WriteLine($"foohandle所调⽤函数的返回值类型{foohandle.Method.ReturnType.ToString()}");
Console.WriteLine("foohandle所调⽤函数参数类型以及参数名分别为:");
Console.WriteLine($"Type:{foohandle.Method.GetParameters()[0].ParameterType},Name:{foohandle.Method.GetParameters()[0].Name}");
Console.WriteLine($"Type:{foohandle.Method.GetParameters()[1].ParameterType},Name:{foohandle.Method.GetParameters()[1].Name}");
Console.Read();
}
static int fooMenthod(int a, Foohandle foohandle) //传给参数函数的就是回调函数
{
return a + foohandle(2, 3);
}
static int add(int a, int b)
{
return a + b;
}
static int multiply(int a, int b)
{
return a * b;
}
}
输出结果:
很明显,不管是声明和使⽤⽅式,都和c++那边⼀样,就连输出结果也差不多,但是很有意思的是,foohandle的类型是Foohandle,且我居然能从foohandle输出所调函数的⼀切信息,包括函数名,返回值,参数类型和参数名,⽽且和c++那边不同的是,我们没有直接操作内存地址,好像看起来是安全的?那么Foohandle类型⼜是什么?
委托是啥?
先来个例⼦:
namespace DelegateSample
{
public delegate void FooHandle(int value);
class Program
{
static void Main(string[] args)
{
FooHandle fooHandle = new FooHandle(multiply);
fooHandle(3);
Console.WriteLine($"fooHandle.Target:{fooHandle.Target},fooHandle.Method:{fooHandle.Method},fooHandle.InvocationListCount:{fooHandle.GetInvocationList().Count()}");
Console.WriteLine("-----------------------------------------------------------------------------------------------------------------------------------");
FooHandle fooHandle1 = new FooHandle(new Foo().Add);
fooHandle1.Invoke(3);
Console.WriteLine($"fooHandle1.Target:{fooHandle1.Target},fooHandle1.Method:{fooHandle1.Metho
d},fooHandle1.InvocationListCount:{fooHandle1.GetInvocationList().Count()}");
Console.Read();
}
static void multiply(int a)
{
Console.WriteLine(a*2);
}
}
public class Foo
{
public void Add(int value)
{
Console.WriteLine(value + 2);
}
}
}
我们看看输出的结果:
很明显,这⾥是⼀个最简单的委托声明,实例化初始化⼀个委托对象,然后调⽤的最简单的场景
我们不关注输出的第⼀⾏,很明显,对象实例化后,可以访问其中的三个公开public的函数成员,
分别是Target(object类型),Method(MethodInfo类型),⽽GetInvocationList函数是⼀个返回值为⼀个Delegate[]的⽆参函数
在上⾯代码,其实我还特地将委托FooHandle声明在Program类外⾯,其实在这⾥我们已经知道委托是什么了,实例化对象,且能够声明在类外⾯,其实它本质就是⼀个类,我们通过反编译来验证:
⼤概是这样,伪代码如下:
public class FooHandle: MulticastDelegate
{
public FooHandle(object @object,IntPtr menthod);//构造⽅法
void Invoke(int value)//调⽤委托,编译后公共语⾔运⾏时给delegate提供的特殊⽅法
void EndInvoke(System.IAsyncResult asyncResult)// 编译后公共语⾔运⾏时给MulticastDelegate提供的特殊⽅法
// 编译后公共语⾔运⾏时给MulticastDelegate提供的特殊⽅法
void BeginInvoke(int value,System.AsyncCallback callback, object obj)
}
我们可以看编译后FooHandle就是⼀个类,且继承MulticastDelegate,且继承链关系在msdn是这样的:
且我们发现上⾯公开的三个函数成员都来⾃于Delegate类,且编译后⽣成了⼏个公共运⾏时提供的特殊⽅法,Invoke⽅法我们很清楚,是来调⽤委托的,我们先来看看委托初始化后的情况,通过查看Delegate的源码,我们发现Delegate有两个构造函数:
1.委托对象初始化构造函数是实例函数:
[SecuritySafeCritical]
protected Delegate(object target, string method)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
if (method == null)
{
throw new ArgumentNullException("method");
}
if (!BindToMethodName(target, (RuntimeType)target.GetType(), method, (DelegateBindingFlags)10))
{
throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTargMeth"));
}
}
2.委托对象初始化构造函数是静态函数:
[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)
writeline函数{
throw new ArgumentNullException("method");
}
RuntimeType runtimeType = target as RuntimeType;
if (runtimeType == null)
{
throw new ArgumentException(Environment.GetResourceString("Argument_MustBeRuntimeType"), "target");
}
BindToMethodName(null, runtimeType, method, (DelegateBindingFlags)37);
}
最后共同调⽤的⽅法:
//调⽤CLR的内部代码
[MethodImpl(MethodImplOptions.InternalCall)]
[SecurityCritical]
private extern bool BindToMethodName(object target, RuntimeType methodType, string method, DelegateBindingFlags flags);
虽然我们看不到BindToMethodName⽅法的实现,已经很明显了,委托对象初始化构造函数是静态函数传参进去BindToMethodName的第⼀个object的target参数为null,那我们⼤概把之前的伪代码的构造函数这么实现了:
伪代码部分:
internal object _target//⽬标对象;
internal IntPtr _methodPtr//⽬标⽅法;
internal IntPtr _methodPtrAux//⽤来判断Target是否为空;
//foolHandle的构造⽅法实现:
public FooHandle(object @object,IntPtr menthod)
{
_methodPtr=menthod;//multiply
_methodPtrAux=1;//只要不等于nul
}
//foolHandle1的构造⽅法实现:
public FooHandle(object @object,IntPtr menthod)
{
_methodPtr=menthod//Add
_methodPtrAux=0//为null
_target=foo;
}
Delegate Target属性源代码部分:
[__DynamicallyInvokable]
public object Target
{
[__DynamicallyInvokable]
get
{
return GetTarget();
}
}
[SecuritySafeCritical]
internal virtual object GetTarget()
{
if (!_methodPtrAux.IsNull())
{
return null;
}
return _target;
}
⽽获取Method的⽅法就不展开了,就是通过反射来获取,那我们已经知道Target和Method属性究竟是怎么回事了,我们还发现没讲到GetInvocationList⽅法是怎么回事?我们知道委托是⽀持多播委托的,也就是⼤概这样,修改上述代码为:
namespace DelegateSample
{
public delegate void FooHandle(int value);
class Program
{
static void Main(string[] args)
{
FooHandle fooHandle = new FooHandle(multiply);
fooHandle(3);
Console.WriteLine($"fooHandle.Target:{fooHandle.Target},fooHandle.Method:{fooHandle.Method},fooHandle.InvocationListCount:{fooHandle.GetInvocationList().Count()}");
Console.WriteLine("----------------------------------------------------------------------------------------------------------------");
FooHandle fooHandle1 = new FooHandle(new Foo().Add);
fooHandle1.Invoke(3);
Console.WriteLine($"fooHandle1.Target:{fooHandle1.Target},fooHandle1.Method:{fooHandle1.Method},fooHandle1.InvocationListCount:{fooHandle1.GetInvocationList().Count()}"); Console.WriteLine();
Console.WriteLine("--------------------------------------------------新增代码------------------------------------------------------");
FooHandle fooHandle2 = new FooHandle(new Program().Minus);
Console.WriteLine($"fooHandle2.Target:{fooHandle2.Target},fooHandle1.Method:{fooHandle2.Method},fooHandle1.InvocationListCount:{fooHandle2.GetInvocationList().Count()}"); fooHandle2(2);
Console.WriteLine("----------------------------------------------------------------------------------------------------------------");
FooHandle fooHandle3 = null;
fooHandle3 += fooHandle;
fooHandle3 =(FooHandle)Delegate.Combine(fooHandle3,fooHandle1);//相当于fooHandle3+=fooHandle1;
fooHandle3 += new Program().Minus;
Console.WriteLine($"fooHandle3.Target:{fooHandle3.Target},fooHandle3.Method:{fooHandle3.Method},fooHandle3.InvocationListCount:{fooHandle3.GetInvocationList().Count()}"); fooHandle3(2);
foreach (var result in fooHandle3.GetInvocationList())
{
Console.WriteLine($"result.Target:{result.Target},result.Method:{result.Method},result.InvocationListCount:{result.GetInvocationList().Count()}");
}
Console.Read();
}
private void Minus(int a)
{
Console.WriteLine(a-1);
}
static void multiply(int a)
{
Console.WriteLine(a * 2);
}
}
public class Foo
{
public void Add(int value)
{
Console.WriteLine(value + 2);
}
}
}
输出结果是:
上⾯新增的代码,我声明了⼀个新的委托变量fooHandle3初始化为null,接着分别⽤三种不同的⽅式将委托或者函数加给fooHandle,之后输出后相当于分别按序调⽤输出了三个⽅法,⽽我们遍历其中的fooHandle3.GetInvocationList()委托数组,输出的也确实三个⽅法,但是注意到了没,我在fooHandle3 += new Program().Minus这段确实没有声明⼀个委托变量,我们可以注意到其中的(FooHandle)Delegate.Combine(fooHandle3,fooHandle1)这句,Combine很明显是需要两个委托变量的,查看编译后的代码我们可以得知到底发⽣了啥?
Il关键代码如下:
//fooHandle3 += fooHandle
IL_00f7: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_00fc: castclass DelegateSample.FooHandle
IL_0101: stloc.3
IL_0102: ldloc.3
IL_0103: ldloc.1
//fooHandle3 =(FooHandle)Delegate.Combine(fooHandle3,fooHandle1)
IL_0104: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_0109: castclass DelegateSample.FooHandle
IL_010e: stloc.3
IL_010f: ldloc.3
//new Program()
IL_0110: newobj instance void DelegateSample.Program::.ctor()
IL_0115: ldftn instance void DelegateSample.Program::Minus(int32)
//new FooHandle()新增了⼀个FooHandle委托变量
IL_011b: newobj instance void DelegateSample.FooHandle::.ctor(object,
native int)
//fooHandle3 += new Program().Minus
IL_0120: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
也就是三种不同⽅式都会被翻译为Combine⽅法,如果是直接+=函数这种情况,后台也会new⼀个委托变量,将⽅法赋值给该变量再加到fooHandle3,那么我们可以知道,最关键的核⼼代码就应该是Delegatebine这个静态⽅法了,我们来看看源码是怎么回事:
Delegate类的:
[__DynamicallyInvokable]
public static Delegate Combine(Delegate a, Delegate b)
{
if ((object)a == null)
{
return b;
}
return a.CombineImpl(b);
}
protected virtual Delegate CombineImpl(Delegate d)
{
throw new MulticastNotSupportedException(Environment.GetResourceString("Multicast_Combine"));
}
MulticastDelegate类的:
[SecurityCritical]
private object _invocationList;//委托链表
[SecurityCritical]
private IntPtr _invocationCount;
[SecuritySafeCritical]
protected sealed override Delegate CombineImpl(Delegate follow)
{
if ((object)follow == null)
{
return this;
}
if (!Delegate.InternalEqualTypes(this, follow))
{
throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTypeMis"));
}
MulticastDelegate multicastDelegate = (MulticastDelegate)follow;
int num = 1;
object[] array = multicastDelegate._invocationList as object[];
if (array != null)
{
num = (int)multicastDelegate._invocationCount;
}
object[] array2 = _invocationList as object[];
int num2;
object[] array3;
if (array2 == null)
{
num2 = 1 + num;
array3 = new object[num2];
array3[0] = this;
if (array == null)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论