事件是C#的基础之一,学好事件对于了解.NET框架大有好处。
事件最常见的比喻就是订阅,即,如果你订阅了我的博客,那么,当我发布新博客的时候,你就会得到通知。
而这个过程就是事件,或者说是事件运行的轨迹。
事件是发散,以我的博客为核心,向所有订阅者发送消息。我们把这种发散称之为[多播]。
最常见的事件用途是窗体编程,在Windows窗体应用程序和WPF应用程序中。
当在窗体中点击按钮,移动鼠标等事件时,相应的后台程序会收到通知,再执行代码。
事件的定义
官方对事件的说明是这样的:类或对象可以通过事件向其他类或对象通知发生的相关事情。
换成正常语言就是,事件可以定义成静态的或普通的,所以事件就可以由声明的对象调用,也可以直接通过类调用静态事件。
事件是C#中的一种类型,除了框架为我们定义好的事件外,我们还可以自定义事件,用event关键字来声明。
下面我们来看最基础的事件定义。
public delegate void TestDelegate(string message);
public event TestDelegate testEvent;
我们首先定义了一个委托,然后利用event关键字,定义一个事件。
整体上看,好像就是在定义一个委托,只是在委托的定义之前,加了个event关键字。
没错,事件的定义就是这样,因为要声明一个事件,需要两个元素:
一,标识提供对事件的响应的方法的委托。
二,一个类,用存储事件的数据。即,事件要定义在类中。
下面我们来为这个事件赋值。
public void Init()
{
testEvent += new TestDelegate(EventSyntax_testEvent);
testEvent += EventSyntax_testEvent;
}
private void EventSyntax_testEvent(string message)
{
Console.WriteLine(message);
}
如代码所示,我们使用了+=这个符号来为事件赋值,赋值的内容是一个委托和一个函数。
其中+=我们将他理解为【添加】。
代码中,我们使用两种赋值模式,但实际上都是为事件testEvent添加一个委。
第二种将函数直接【添加】到事件中,编译时也会把函数转换成委托【添加】到事件中。
系统提供事件
C#的框架都很经典,而每个经典框架都为我们提供了一些经典事件。
由于事件必须[标识响应方法的委托],所以这些事件所使用的委托都有一个共同的特点,命名中包含Event。
比如EventHandler,CancelEventHandler,RoutedEventHandler,ContextMenuEventHandler等。
其中最经典的就是EventHandler和RoutedEventHandler。
EventHandler:
EventHandler定义如下
[SerializableAttribute]
[ComVisibleAttribute(true)]
public delegate void EventHandler(
object sender,
EventArgs e
)
他包含了两个参数,即当我们为事件添加EventHandler委托后,再去触发该事件;被触发的委托将得到object sender和EventArgs e两个参数。
sender:代表源,即触发该事件的控件。
e:代表事件参数,即触发该事件后,事件为被触发的委托,传递了一些参数,以方便委托在处理数据时,更便捷。
根据这个原理,我们可以分析出很多东西。
比如,当控件DataGrid的事件被触发时,只要查看一下sender的真实类型,就可以知道,到底是DataGrid触发的事件,还是DataGridRow或DataGridCell触发的了。
RoutedEventHandler:
RoutedEventHandler即路由事件,他的定义如下
public delegate void RoutedEventHandler(
Object sender,
RoutedEventArgs e
)
RoutedEventHandler也为我们提供了sender和e两个参数。
但RoutedEventHandler特别之处是,他的sender并不一定是真实的源,因为他是一个冒泡路由事件,即上升事件。
这里如果大家有好奇心去看官方文档,那么会在相关的介绍中看到两个单词sender和source。
通过这两个单词,我们会清晰的了解路由事件。简单描述一下sender和source,它们一个是发送者,一个是源。
在EventHandler中,sender即source,因为它是直接事件。而在冒泡事件中,sender不一定等于source。即发送者不一定是源。
下面我们用WPF来看看路由事件。
我们首先在XAML页面定义一个RadioButton按钮,然后设置他的模板是Button。然后分别定义各自的Click方法。
Xaml页面如下:
<RadioButton Click="btnParent_Click">
<RadioButton.Template>
<ControlTemplate>
<StackPanel>
<TextBlock Text="我的名字" ></TextBlock>
<Button Content="Kiba518" Click="btnClild_Click" ></Button>
</StackPanel>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
cs文件事件如下:
private void btnParent_Click(object sender, RoutedEventArgs e)
{
string type = sender.GetType().ToString();//RadioButton
}
private void btnClild_Click(object sender, RoutedEventArgs e)
{
string type = sender.GetType().ToString();//Button
}
运行起来,我们点击按钮,通过断点我们可以看到,我们点击的按钮触发了btnClild_Click和btnParent_Click事件。
顺序是先btnClild_Click后btnParent_Click。
通过获取sender的类型,我也可以看到,btnClild_Click的sender类型是Button,而btnParent_Click的sernder类型是RadioButton。
事件驱动编程
事件驱动编程这个概念给我的感觉很怪,因为一直用C#,而C#的很多框架都是事件驱动的,所以一直觉得事件驱动是理所当然。
而当事件驱动设计这个词经常出现后,反而感觉怪怪的。
就好像,天天吃大米饭,突然有一天,所有人都说大米饭好香的感觉一样,你一听就感觉怪怪的。
因为事件驱动对于C#开发而言,实在太普通了。当然,这也得益于微软框架做的实在是太好了。
所以,我也不知道如何在C#里讲事件驱动编程。因为使用C#的框架就是使用事件驱动编程。
事件和委托到底是什么关系?
事件是用来多播的,并且用委托来为事件赋值,可以说,事件是基于委托来实现的。
但委托中也有多播,那为什么要单独弄出来一个事件呢?
首先,存在即合理,事件一定有他存在的意义。
事件存在的意义
我对事件存在的意义是这样理解的。我们在C#编写框架时,几乎不用委托的多播,因为委托的多播和事件存在严重的二义性。虽然编写框架的人学会了使用委托的多播,但使用框架的同事可能并还不太熟练,而且C#框架中,大多是使用事件来进行多播的。
所以委托的多播和事件一起使用的框架,会造成使用这个框架的初级开发者很多困惑,而这种困惑,会产生很多不必要的问题。
比如, 你定义了一个委托,另一个开发者用这个委托做了个多播,当第三个开发者来维护这段代码时,如果他是新手,不了解委托的多播,那就很有可能只修改了委托调用的代码。而没有去同步多播这个委托的代码。那系统就产生了隐藏的bug。
那么,事件和委托到底是什么关系呢?
事件与委托的确存在千丝万缕的关系,怎么讲都是正确的。但,C#开发者只需要记住,他们俩没关系即可。在C#事件是事件,委托是委托。两者就如同int和string一样,没有任何关系。
原因很简单,学习的过程中尽量降低概念混淆。而且,在C#开发中,好的架构者也通常会将事件和委托分离,所以,就认为事件和委托没有关系即可。
结语
其实事件很好理解,一点不复杂。我在写这篇文章的过程中,也没想到什么特别的或者说比较高级的用法。
但真实的应用场景中,我的感觉是,随着MVVM的成长,事件其实在被逐渐抛弃。虽然微软做了很多经典的事件驱动框架。但那都是过去了。
比如WPF虽然支持事件驱动,但MVVM在WPF下的表现堪称完美,所以WPF下的事件几乎没有人用了。
再比如前端的Angularjs等框架,提供了优质的MVVM使用效果,也让新的前端设计师逐渐放弃了事件。
所以,事件在未来的编程中,很可能将不在有那么重要的地位了。但学好事件,对于我们理解微软框架,还是有很大帮助的。
注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
共同学习,写下你的评论
评论加载中...
作者其他优质文章