今天在博客园里面搜索了关于Delegate的文章,已经有很多好的博文了。最近我也研究了一下关于Delegate(委托)的背后的东西,也希望把研究的心得写出来供笔记参考。
我们可能会听别人谈起过C#事件,至少我曾经听同行这样描述过:
1、 C#事件是特殊的委托
2、 C#中使用委托模型来实现事件的。
3、 C#中的委托是一个引用类型,可以把它看成一个特殊的”类”。
所以我这里就通过C#事件来说Delegate(委托)。先看一段示例代码:
public class CharlesLog
{
public event ProcessEventHandler processEvent;
public void ProcessHandler(string name)
{
if (processEvent != null)
{
processEvent(name);
}
else
{
processEvent = new ProcessEventHandler(DefaultHandler);
processEvent(name);
}
}
public void DefaultHandler(string name)
{
Console.WriteLine("default Process!");
}
}
class Program
{
static void Main(string[] args)
{
CharlesLog charlesLog = new CharlesLog();
charlesLog.processEvent += new ProcessEventHandler(CustomProcess);
charlesLog.ProcessHandler("CharlesChen");
}
public static void CustomProcess(string name)
{
Console.WriteLine("custom Process!");
}
}
根据这个Demo,针对委托和事件而言,需要注意一些要点,我这里列举出来:
1、 上面的委托委托实例化用的是.Net 1.1的语法:
(注:)随着Net版本升级,委托实例化的语法简化,在2.0有匿名方法,以及3.0的Lambda表达式更能符合人类语言。
2、 申明事件的语法是:”修饰符” event <委托类型> 事件名”,实际上是比申明委托实例多了一个event关键字而已。
3、 使用”+=”符合的时候会根据委托进行自动判断:
如果此时委托还没有实例化(null),它会自动用+=来对委托实例化。
如果委托已经实例化,它用+=来把函数注册委托指定的委托链上。
4、 事件(上面的processEvent)注册事件处理程序只能用+=或者-=,不能用=。否则会出现
CharlesLog.processEvent只能出现在+=或-=的左边(CharlesLog类中使用除外)。
注意委托(delegate)是可以用=号来实例化
OK,上面提出的几点都是平常我们要注意的地方。如第4点,也许你会问为什么委托可以用=,而事件却不能用=号,而只能用+=和-=呢?通过Reflactor工具来看一看event背后发生的事情:
通过C#编译器会编译成:
可以看到,尽管这里定义的processEvent申明为public,但是实际上processEvent会被编译成私有字段(到这里你应该晓得为什么对于事件你不能在外部用”=”,但是对于内部可以用”=”)。因为它根本不允许在CharlesLog的外面已赋值的方式来访问。我们进一步看一下:
[MethodImpl(MethodImplOptions.Synchronized)]
public void add_processEvent(ProcessEventHandler value)
{
this.processEvent = (ProcessEventHandler)Delegate.Combine(this.processEvent, value);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void remove_processEvent(ProcessEventHandler value)
{
this.processEvent = (ProcessEventHandler)Delegate.Remove(this.processEvent, value);
}
OK,现在已经明确了,processEvent确实是ProcessEventHandler类型的委托,只不过不管是申明为public,它总是被申明为Private。另外,它还增加了两个方法,分别是add_processEvent和remove_processEvent方法,这两个方法分别用于注册委托类型的方法和取消注册。实际上也就是:“+=”对应add_processEvent,“-=”对应remove_processEvent。而这两个方法的访问限制取决于申明事件的访问修饰符。
我们也可以看到在add_processEvent()方法内部,实际上是调用了System.Delegate的Combine()的静态方法,这个方法用于将当前的变量添加到委托链中。
我们前面说:委托实际上是一个类。也就是说当编译器遇到
代码的时候,会生成下面这样一个类:
{
[MethodImpl(0, MethodCodeType = MethodCodeType.Runtime)]
public ProcessEventHandler(object @object, IntPtr method);
[MethodImpl(0, MethodCodeType = MethodCodeType.Runtime)]
public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object);
[MethodImpl(0, MethodCodeType = MethodCodeType.Runtime)]
public virtual void EndInvoke(IAsyncResult result);
[MethodImpl(0, MethodCodeType = MethodCodeType.Runtime)]
public virtual void Invoke(string name);
}
那我们可能还会想:那根据这委托生成的类,我们在Main函数中实注册事件的时候是怎样调用ProcessEventHandler委托的呢?如果要分析的话,我们可以根据IL代码来看到基本的过程:
上面这幅图IL图只说明了我们把CustomProcess加到委托链中,那么当我们调用委托时候,它是根据什么去调用委托链中的方法呢?下面我们看一下CharlesLog中的ProcessHandler()方法。
OK,上面这两幅图就是前面代码通过C#编译器产生的IL代码样例,现在我们可以清楚的理解最前面说的:
1、 C#事件是特殊的委托
2、 C#中使用委托模型来实现事件的。
3、 C#中的委托是一个引用类型,可以把它看成一个特殊的”类”。
OK,本文就写到这里。主要是通过Demo去了解了C#事件背后发生的事情。关于C#委托与事件的详细介绍可以从博客园中其他好文获取:
1、浅谈C#委托和事件及续
4、委托揭秘