委托研究






3.94/5 (28投票s)
一篇关于 C# 中委托的文章。
引言
我们中的许多人对委托已经有了一些了解。但是,让我们深入研究一下,看看 CLR/运行时和编译器是如何解释委托的,以及当你声明和操作委托时会发生什么。
委托是一种引用类型,因此它不存储实际的值,而是存储堆中某个位置的对象的地址。为了简化对委托的理解,编译器以一种不同于 CLR/运行时解释的方式来表示委托。我们将首先研究编译器如何公开委托,然后从 CLR/运行时的角度深入探讨委托的一些实际情况。
委托和 .NET 编译器
为了简化生活,大多数 .NET 编译器将委托象征性地表示为指向函数的指针,就像 C++ 中的函数指针一样。因此,在 C# 中, you declare delegates as follows
public delegate void MyDelegate (int someParam, string anotherParam);
从 C# 编译器的角度来看,MyDelagate
是一个委托,它可以存储一个具有相似签名的函数的地址。
存储函数地址的能力使委托成为理想的:
- 回调功能
- 事件建模
- 针对一个方法有多个实现
尽管 C# 编译器将委托公开为引用类型,但您不能使用 new
运算符来实例化委托;相反,可以通过以下方式实例化委托:
- 为委托提供方法的名称。这类委托被称为“命名委托”。
void DoSomething(int someParam, string anotherParam) { //Method implementation } MyDelegate del = DoSomething; //del is a Named Delegate holding //the address of function DoSomething.
- 为委托提供方法的实现。这类委托被称为“匿名委托”。
del = delegate(int someParam, string anotherParam) { //Method implementation };
调用命名委托或匿名委托与调用函数相同
Del (29, "some value");
命名委托
命名委托可以存储实例方法的地址以及静态方法的地址。如果方法的返回类型继承自委托中定义的返回类型,则该委托称为 **协变委托**。如果方法的参数是委托中定义的参数的基本类型,则该委托称为 **逆变委托**。
public delegate ICollection MyDelegate (int someParam,
string anotherParam);
IList DoSomething()
{
//Method implementation
}
MyDelegate del = DoSomething;
//As IList is child of ICollection thus,
//Del is covariance delegate.
public delegate ICollection MyDelegate (IList param);
IList DoSomething(ICollection param)
{
//Method implementation
}
MyDelegate del = DoSomething;
//As IList is child of ICollection thus,
//Del is contravariance delegate.
匿名委托
如果匿名委托引用了一些局部变量,那么该变量的生命周期将被延长到委托的生命周期。
public delegate void MyDelegate (int someParam, string anotherParam);
MyDelegate del
void DoSomething()
{
int n;
del = delegate(int someParam, string anotherParam)
{
n++;
}
}
//In above example life of local variable n
//would be same as life of anonymous delegate del.
委托和 CLR/运行时
.NET 编译器使我们在处理委托时更加轻松。实际上,在 CLR/运行时中,既没有指向函数的指针的概念,也没有“委托”一词的意义。相反,CLR 提供了一个抽象类 System.Delegate
,它持有静态方法或类实例以及该类的实例方法的引用。System.Delegate
具有两个属性。
Target
:持有委托调用实例方法时所基于的实例的引用。如果该方法是静态方法,则此属性将持有null
值。Method
:保存表示委托的方法的MethodInfo
类的实例。
委托还公开了 DynamicInvoke
函数来调用 Method
属性所表示的方法。此函数接受一个对象数组,这些对象是要传递给目标方法的参数。如果目标方法不接受任何参数,则 DynamicInvoke
函数的参数将为 null
。
CLR 还公开了一个名为 System.MultiCastDelegate
的抽象类,它继承自 System.Delegate
。System.MultiCastDelegate
维护一个名为调用列表的委托对象列表。当调用多播委托时,调用列表中的所有委托都会被同步调用。System.MultiCastDelegate
公开 Combine
方法以将委托添加到调用列表中。
当你在 C# 中定义委托时,C# 编译器会创建一个名为该委托的新类,该类继承自 System.MultiCastDelegate
。
public delegate ICollection MyDelegate (IList param);
上面的语句导致 C# 编译器在你的程序集中添加一个新类。
public class MyDelegate : System.MultiCastDelegate
你可以使用 Ildasm 工具打开你的程序集来查看这个类。
当你将一个方法分配给委托时,会使用 Target
和 Method
作为参数来创建编译器生成的类的实例。如果该方法是 static
的,则将 null
传递给 Target
参数。
由于编译器生成的类继承自 System.MultiCastDelegate
,因此委托可以持有多个方法的地址。C# 公开了一个简单的方法来将方法添加到底层多播委托对象的调用列表中。
MyDelegate del;
del += DoSomething;
del += DoAnotherThing;
在内部,C# 编译器会调用编译器生成的委托类的 Combine
方法来将方法添加到调用列表中。
当你调用委托时,C# 会在内部调用编译器生成类的 DynamicInvoke
方法。编译器生成的类还公开了用于异步调用目标方法的方法。
如果调用委托时也提供了 Target
,则该委托称为开放实例委托。