C#事件由浅至深简析
2014-04-04 11:28 hailants 阅读(1923) 评论(6) 编辑 收藏 举报0.简述
C#中的事件可以说是应用相当多的一种机制,这里从浅至深了解下C#中的事件机制:
- 事件的简单应用
- 自定义事件
- C#事件机制
- 使用事件不得不注意的事
PS:公司上网机上没有IDE工具,所以,有些代码只能是简单写一下,无法给出运行截图和完整测测试项目了;大家见谅!
1.事件的简单应用
在VS的IDE中最简单的事件应用莫过于如下:
-
创建一个WINFORM项目
-
从工具栏中拖一个"按钮"控件到form1界面上
-
双击按钮控件,VS会自动创建按钮单击事件回调函数,并将函数与按钮单击事件关联,并跳转到事件回调函数button1_Click的代码编辑界面。
-
在button1_Click函数中,填写以下代码:
MessageBox.Show("Hello,这是事件回调结果!");
-
按F5看看运行效果吧,点击按钮,弹出什么了?对的,是一个对话框:
Hello,这是事件回调结果!
这是最简单的事件应用。接下来让我们看看在代码中如何应用事件:
- 还是刚才那个按钮,让我们按F7切换到代码编辑界面吧。
- 在Form1的构造函数中,InitializeComponent();这一句下面添加我们的事件代码,使得整个构造函数是下面的样子:
public Form1() { InitializeComponent(); button1.MouseHove+=new EventHandler(Button1_MouseHover); }
- 哦,感谢微软的工程师们,在编写上面代码过程中,IDE会自动生成最终的事件回调函数,我们只需要填写函数内容即可:
private void Button1_MouseHover(object sender,EventArgs e) { MessageBox.Show("嘿,你怎么还不点?"); }
- 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; }
PS:上面的代码效果是什么呢?调用UserEventClass 实例的TriggerEvent方法即会触发该实例的EventTriggered事件。至于为什么使用OnEventTrigger来最终完成事件的触发,会在下面做出解释。
然后就可以象使用控件中的事件一样来使用我们自定义的事件了。
当然,更复杂的自定义事件方式可以在博客园找到很多资料,基本上都大同小异,例如:
在上面的文章2中有讲解如何安全引发事件,这就是上面自定义事件代码中使用OnEventTrigger来最终完成事件的触发的原因。同时,OnEventTrigger被标为protected virtual,这样在UserEventClass的子类中,可以重写OnEventTrigger方法来进行需要的修改。
3.C#事件机制
所有的教材里面都会先讲解C#委托,然后才会讲解C#事件,因为事件本质上仍然是由委托完成的,其神奇的表现仅仅是微软工程师们给我们的语法糖(PS:可能有点偏激)。委托与事件之间的关联可以参看下面的文章:
这里面要深究的其实应该是多播委托的委托链调用机制,拿出我们的反编译神器Reflecter,看看Combine里面有什么?
- Delegate.Combine里面调用了参数1的Delegate. CombineImpl方法,这一方法在普通的Delegate类中会抛出异常,而MulticastDelegate类重写了该方法。对该方法进行分析可以得出:将本实例及传入的实例这两个MulticastDelegate中的_invocationList列表组合,并以组合出的新列表创建一个新的MulticastDelegate返回。因此Combine返回的将是一个新的MulticastDelegate。_invocationList是一个List<Object>,这一点稍微有点奇怪,而且MulticastDelegate还提供了一个GetInvocationList方法返回Delegate数组,感觉有点多次一举,还得装箱拆箱。
- EventHandler继承自MulticastDelegate,并实现了Invoke,BeginInvoke等方法,但是很遗憾,方法内容都是CLR内部调用了,无法探究,但是从MulticastDelegate的构成大略可以猜测出,是遍历_invocationList,并逐个调用每个实例的Delegate.Invoke方法。
4.使用事件不得不注意的事
上面粗略描述了事件的使用,自定义及其深入机制,下面说说使用事件时需要注意的几个问题:
- 都碰到过”线程间操作无效”的异常吧,没错,即便在事件中改变界面控件的属性,调用方法等,也会碰到这个提示,但是有人会说,我在按钮事件中去改变lable的值,为什么没有异常呢?OK,还是委托的机制,委托其实是方法指针,调用委托就意味着委托的实际方法体运行在调用方一致的线程中,UI控件运行在什么线程下呢?界面线程,所以UI事件间进行操作是不会报异常的。如果你用一个SOCKET,如TCPClient,响应接受到数据的事件的话,那么这个事件将不会运行在界面主线程下,于是你就会看到”线程间操作无效”。
- 如果事件回调中有一个长时间执行的代码会如何呢?在按钮事件回调里写过数据库操作的人都有同感:卡……。这意味着事件是同步回调而不是异步回调,所以,把你的代码移开吧,并且仔细检查你的代码,保证回调函数中没有诸如死循环之类的……
- 如果关联了多个事件回调,那么他们的执行有顺序么?按照官方说法:不保证回调依照绑定顺序调用。不过实际经验来说大多数是和绑定顺序相同的,同时,一个阻塞了,后面的都不会执行;一个回调异常将影响所有回调。