来自 Learn hard C# 学习笔记 这本书的例子。
委托
C# 中的委托可以理解为函数的一个包装,它使得 C# 中的函数可以作为参数来被传递。
委托的定义和方法的定义类似,只是在定义的前面多了一个 delegate
关键字。即
public delegate void MyDelegate(int para1, string para2);
委托与方法
class Program
{
static void Main(string[] args)
{
Program p = new Program();
p.Greeting("鹿晗", p.ChineseGreeting);
p.Greeting("Tommy Li", p.EnglishGreeting);
Console.Read();
}
// 定义委托类型
public delegate void GreetingDelegate(string name);
//有了委托之后,可以像下面这样实现打招呼方法
public void Greeting(string name, GreetingDelegate callback)
{
//调用委托
callback(name);
}
//英国人打招呼方法
public void EnglishGreeting(string name)
{
Console.WriteLine("Hello," + name);
}
//中国人打招呼方法
public void ChineseGreeting(string name)
{
Console.WriteLine("你好," + name);
}
}
主要是在 p.Greeting 这个方法中,将一个方法传递给一个委托类 GreetingDelegate,这个委托来直接调用。
即委托的作用:将函数作为参数传递给另一个方法。
委托的本质:
这句代码 callback(name); 实际上是隐式调用了 Invoke方法。
即 IL 代码为 :callback.Invoke(name);
所以你可以看到,委托是类类型,委托实例可以调用实例方法。
事件
事件是成员。An envent is a member.
和方法、属性一样,事件是类或结构的成员。(有点类似于类中的属性)
“事件” 存在的首要理由和 “属性” 差不多--它们添加了一个封装层,实现发布/订购模式(publish/subscribe pattern)。
例:当声明了一个事件。
// 自定义了一个委托(这是类,可以与类平行)
public delegate void MarryHandler(string msg);
// 使用自定义委托定义事件 (这相当于成员,只能在类里面定义)
public event MarryHandler MarryEvent;
从 IL 可以看出,MarryHandler 委托被编译成一个名为 MarryHandler 的类,而 MarryEvent 事件则被编译成如下所示的 IL 代码
.event EventDemo.Program/MarryHandler MarryEvent
{
.addon instance void EventDemo.Program::add_MarryEvent(class EventDemo.Program/MarryHandler)
.removeon instance void EventDemo.Program::remove_MarryEvent(class EventDemo.Program/MarryHandler)
} // end of event Program::MarryEvent
从IL代码可以看出,C#事件被编译成包含两个公共方法的代码段,一个带有add_ 前缀,另一个带有 remove_ 前缀,前缀后面是C#事件的名称。
查看 add_MarryEvent() 的IL代码,就会发现该方法是通过 调用 Delegate.Combine() 方法来实现的。相应地,remove_MarryEvent 方法也将间接调用Delegate.Remove()方法。
.method public hidebysig specialname instance void
add_MarryEvent(class EventDemo.Program/MarryHandler 'value') cil managed
{
...
IL_000b: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
...
} // end of method Program::add_MarryEvent
同时,还有一个 MarryEvent 字段的定义。MarryEvent : private class EventDemo.Program/MarryHandler。查看这句 IL 代码发现这是一个 field。
.field private class EventDemo.Program/MarryHandler MarryEvent
从前面的分析可以得出,C# 中的事件是一个特殊的多路广播委托,事件默认含有一个私有的委托类型变量,该变量用于保存对事件处理方法的引用,且该委托类型的变量为私有,只能从定义该事件的类中进行访问。
由事件的IL代码段,我们可以联想到类中属性的定义。与事件不同,属性中定义了 set 访间器和 get 访问器,两个访问器的本质是以 “get” 和 “set” 为前缀的两个方法。属性用于对类中的私有字段进行访问,而 C# 事件也可以看作是“委托字段的属性”,因此可以通过事件来对私有的委托字段进行访问,这也是 C# 事件特性存在的原因。C# 事件机制符合面向对象的封装特性它使得应用程序代码更加安全。
其他解释(翻译文体):
(通常,我们不希望其他代码能直接设置字段值;最起码也要先由所有者(owner)对新值进行验证。同样,我们通常不希望类外部的代码随意更改(或调用)一个事件的处理程序)。(感觉就是封装的意思)
字段风格的事件使所有这些的实现变得更易阅读 -- 只需一个声明就可以了。编译器会将声明转换成一个具有默认add/remove实现的事件和一个私有委托类型的字段。类内的代码能看见字段;类外的代码只能看见事件。这样一来,表面上似乎能调用一个事件,但为了调用事件处理程序,实际做的事情是调用存储在字段中的委托实例。