代码改变世界

C#事件由浅至深简析

2014-04-04 11:28  hailants  阅读(1923)  评论(6编辑  收藏  举报

0.简述

  C#中的事件可以说是应用相当多的一种机制,这里从浅至深了解下C#中的事件机制:

  1. 事件的简单应用
  2. 自定义事件
  3. C#事件机制
  4. 使用事件不得不注意的事

PS公司上网机上没有IDE工具,所以,有些代码只能是简单写一下,无法给出运行截图和完整测测试项目了;大家见谅!

1.事件的简单应用

  在VS的IDE中最简单的事件应用莫过于如下:

  1. 创建一个WINFORM项目
  2. 从工具栏中拖一个"按钮"控件到form1界面上
  3. 双击按钮控件,VS会自动创建按钮单击事件回调函数,并将函数与按钮单击事件关联,并跳转到事件回调函数button1_Click的代码编辑界面。
  4. 在button1_Click函数中,填写以下代码:
    MessageBox.Show("Hello,这是事件回调结果!");
  5. 按F5看看运行效果吧,点击按钮,弹出什么了?对的,是一个对话框:
                                Hello,这是事件回调结果!

  这是最简单的事件应用。接下来让我们看看在代码中如何应用事件:

  1. 还是刚才那个按钮,让我们按F7切换到代码编辑界面吧。
  2. 在Form1的构造函数中,InitializeComponent();这一句下面添加我们的事件代码,使得整个构造函数是下面的样子:
    public Form1()
    {
        InitializeComponent();
        button1.MouseHove+=new EventHandler(Button1_MouseHover);
    }
  3. 哦,感谢微软的工程师们,在编写上面代码过程中,IDE会自动生成最终的事件回调函数,我们只需要填写函数内容即可:
    private void Button1_MouseHover(object sender,EventArgs e)
    {
            MessageBox.Show("嘿,你怎么还不点?");
    }
  4. OK,按F5看看运行效果吧(把鼠标放到按钮上等一会)。

2.自定义事件

  如何在自定义的类中定义事件呢?参看下面的代码:

public class UserEventClass
{
    public void TriggerEvent()
    {
        OnEventTrigger();
    }
    protected virtual void OnEventTrigger()
    {
        var handler= EventTriggered;
        if(handler!=null)
        {
            handler(this,new EventArgs());
        }
    }
    public event EventHandler EventTriggered;
}
View Code

  PS:上面的代码效果是什么呢?调用UserEventClass 实例的TriggerEvent方法即会触发该实例的EventTriggered事件。至于为什么使用OnEventTrigger来最终完成事件的触发,会在下面做出解释。

  然后就可以象使用控件中的事件一样来使用我们自定义的事件了。

  当然,更复杂的自定义事件方式可以在博客园找到很多资料,基本上都大同小异,例如:

C#事件(event)解析-徐洪军

  C#事件-张雪飞

  在上面的文章2中有讲解如何安全引发事件,这就是上面自定义事件代码中使用OnEventTrigger来最终完成事件的触发的原因。同时,OnEventTrigger被标为protected virtual,这样在UserEventClass的子类中,可以重写OnEventTrigger方法来进行需要的修改。

3.C#事件机制

  所有的教材里面都会先讲解C#委托,然后才会讲解C#事件,因为事件本质上仍然是由委托完成的,其神奇的表现仅仅是微软工程师们给我们的语法糖(PS:可能有点偏激)。委托与事件之间的关联可以参看下面的文章:

C# 中的委托和事件-张子阳

  这里面要深究的其实应该是多播委托的委托链调用机制,拿出我们的反编译神器Reflecter,看看Combine里面有什么?

  1. Delegate.Combine里面调用了参数1的Delegate. CombineImpl方法,这一方法在普通的Delegate类中会抛出异常,而MulticastDelegate类重写了该方法。对该方法进行分析可以得出:将本实例及传入的实例这两个MulticastDelegate中的_invocationList列表组合,并以组合出的新列表创建一个新的MulticastDelegate返回。因此Combine返回的将是一个新的MulticastDelegate。_invocationList是一个List<Object>,这一点稍微有点奇怪,而且MulticastDelegate还提供了一个GetInvocationList方法返回Delegate数组,感觉有点多次一举,还得装箱拆箱。
  2. EventHandler继承自MulticastDelegate,并实现了Invoke,BeginInvoke等方法,但是很遗憾,方法内容都是CLR内部调用了,无法探究,但是从MulticastDelegate的构成大略可以猜测出,是遍历_invocationList,并逐个调用每个实例的Delegate.Invoke方法。

4.使用事件不得不注意的事

  上面粗略描述了事件的使用,自定义及其深入机制,下面说说使用事件时需要注意的几个问题:

  1. 都碰到过”线程间操作无效”的异常吧,没错,即便在事件中改变界面控件的属性,调用方法等,也会碰到这个提示,但是有人会说,我在按钮事件中去改变lable的值,为什么没有异常呢?OK,还是委托的机制,委托其实是方法指针,调用委托就意味着委托的实际方法体运行在调用方一致的线程中,UI控件运行在什么线程下呢?界面线程,所以UI事件间进行操作是不会报异常的。如果你用一个SOCKET,如TCPClient,响应接受到数据的事件的话,那么这个事件将不会运行在界面主线程下,于是你就会看到”线程间操作无效”。
  2. 如果事件回调中有一个长时间执行的代码会如何呢?在按钮事件回调里写过数据库操作的人都有同感:卡……。这意味着事件是同步回调而不是异步回调,所以,把你的代码移开吧,并且仔细检查你的代码,保证回调函数中没有诸如死循环之类的……
  3. 如果关联了多个事件回调,那么他们的执行有顺序么?按照官方说法:不保证回调依照绑定顺序调用。不过实际经验来说大多数是和绑定顺序相同的,同时,一个阻塞了,后面的都不会执行;一个回调异常将影响所有回调。