事件(event)是在委托的基础上实现的一种“通知机制”。比如,当一个按钮被点击,就是一个事件,事件的发生可以通知相关的程序进行处理。事件相当于其他语言中的回调函数,或事件监听类。
对于Button类来说,Click是其中一个特殊的属性,它就代表一个事件,它的类型是EventHandler
委托,是的,关于事件有几个关键点:
- 在一个类中定义一个事件,事件的类型是一个委托类型
- 在外面用
+=
来注册这个事件,即将一个外部方法关联上了 - 在事件源所在的类中,在一定条件下(即事件发生了)来调用这个委托,实际上是调用了外部方法,即相当于通知到外部了。在调用时,还传递了事件发生时的具体详情。
自定义事件
事件机制的工作过程如下:
关心某事件的对象向能发出事件的对象进行事件处理程序的注册。当事件发生时,会调用所有注册的事件处理程序。事件处理程序要用委托来表示。可以认为,事件就是委托实例,只不过为了便于应用,C#在委的基础上进行了一些增强,在使用方式上进行了一些限定。下面是自定义事件的一些步骤:
1.事件的声明
事件是类、结构及接口的成员,声明格式如下:
修饰符 event 委托类型名 事件名;
其中修饰符可以是访问修饰符以及其他修饰符(如static
,new
,abstract
等)。
2.事件的注册与移除
事件注册的目的是告诉事件的发出者,需要通知的对象。例如,按钮单击后,某个图片要放大。事件注册的实质,就是向委托的调用列表中添加方法。
注册事件要使用 +=
运算符:
事件名 += 委托实例;
事件名 += new 委托类名(方法名);
移除一个事件注册使用 -=
运算符:
事件名 -= 委托实例;
事件名 -= new 委托类名(方法名);
注意,在声明事件的类的外部,对于事件的操作只能用 +=
和 -=
,而不能用其他运算符,如赋值(=
)、判断是否为空(==
)等。
3.事件的发生
事件的发生,就是对事件相对应的委托的调用,也就是委托的调用列表中所包含的各个方法的调用。格式如下:
事件名(参数);
事件的典型应用
C#中允许各种委托应用于事件中,但典型的应用中,委托一般是这样的格式:
delegate void 委托名 (object sender, EventArgs e);
其中,返回类型为void
,委托名有两个参数,分别表示事件的发出者以及事件发生时的一些参数。为了表示具体的参数,一般要继承EventArgs
,加上更多的属性和方法。
如此说来,自定义事件,要有六步曲:
- ① 定义具体的事件参数类型,可以从
EventArgs
继承; - ② 定义一个委托类型,如果不想自定义委托,则可以使用系统中定义的泛型委托
EventHandler <TEventArgs>
; - ③ 在事件源类中定义一个事件,使用
event
关键字和委托类型; - ④ 在事件源类中的合适地方(即事件发生的时候),生成事件参数,并调用事件。
- ⑤ 在事件的订阅者中,写一个事件方法来表示事件发生时在执行的任务;如果不写方法名,也可以使用Lambda表达式或匿名方法;
- ⑥ 在事件订阅者中,使用
+=
来注册该事件方法。
示例DownloadWithEvent.cs
中,爬虫程序:
using System;
namespace ConsoleApp6事件_爬虫示例_
{
public delegate void DownloadStartHandler(object sender, DownloadStartEventArgs e); // 1.声明委托, 公用的
public delegate void DownloadEndHandler(object sender, DownloadEndEventArgs e);
public delegate void DownloadingHander(object sender, DownloadingEventArgs e);
public class DownloadStartEventArgs
{
public string Url { get => _url; set => _url = value; }
private string _url;
public DownloadStartEventArgs(string url) { this._url = url; }
}
public class DownloadEndEventArgs
{
public string Url { get => _url; set => _url = value; }
private string _url;
public long ByteCount { get => _byteCount; set => _byteCount = value; }
private long _byteCount;
public DownloadEndEventArgs(string url, long size) { this._url = url; this._byteCount = size; }
}
public class DownloadingEventArgs
{
public string Url { get => _url; set => _url = value; }
private string _url;
public double Percent { get => _percent; set => _percent = value; }
private double _percent;
public DownloadingEventArgs(string url, double percent) { this._url = url; this._percent = percent; }
}
public class Crawler
{
public event DownloadStartHandler DownloadStart; // 2.声明事件, 在一个类中
public event DownloadEndHandler DownloadEnd;
public event DownloadingHander Downloading;
public string Name { get => name; set => name = value; }
private string name;
private string site;
public Crawler(string name, string site) {
this.name = name;
this.site = site;
}
public void Craw() {
while (true) {
string url = GetNextUrl();
if (url == null) break;
long size = GetSizeOfUrl(url);
//下载开始的事件发生
if (DownloadStart != null) {
DownloadStart(this, new DownloadStartEventArgs(url));
//Console.WriteLine("不为空------------------------");
} else
break;
for (long i = 0; i < size + 1024; i += 1024) {
// 下载数据...
System.Threading.Thread.Sleep(100);
double percent = (int)(i * 100.0 / size);
if (percent > 100) percent = 100;
//下载数据的事件发生
if (Downloading != null) {
Downloading(this, new DownloadingEventArgs(url, percent));
}
}
//下载结束的事件发生
if (DownloadEnd != null) {
DownloadEnd(this, new DownloadEndEventArgs(url, size));
}
}
}
private string GetNextUrl() {
int a = rnd.Next(10);
if (a == 0) return null;
return site + "/Page" + a + ".htm";
}
private long GetSizeOfUrl(string url) {
return rnd.Next(3000 * url.Length);
}
private Random rnd = new Random();
}
class Program
{
static void Main(string[] args) {
Console.WriteLine("Hello World!");
Crawler crawler = new Crawler("Crawer101", "http://www.gxust.edu.cn/");
// csharp1.0要写委托
crawler.DownloadStart += new DownloadStartHandler(ShowStart); // 3.注册事件, 在别的类中
crawler.DownloadEnd += new DownloadEndHandler(ShowEnd);
crawler.Downloading += new DownloadingHander(ShowPercent);
crawler.Craw();
}
private static void ShowStart(object sender, DownloadStartEventArgs e) {
Console.WriteLine((sender as Crawler).Name + "开始下载" + e.Url);
}
private static void ShowEnd(object sender, DownloadEndEventArgs e) {
Console.WriteLine("\n\r下载" + e.Url + "结束, 其下载" + e.ByteCount + "字节");
}
private static void ShowPercent(object sender, DownloadingEventArgs e) {
Console.WriteLine("\r下载" + e.Url + "......." + e.Percent + "%");
}
}
}
事件的语法细节
C#的事件是在委托的基础上进行处理的,可以将它看成是具有委托类型的一个变量或属性,事实上,事件不是一个简单变量,而是进行了一个重要的扩展,及在事件声明中还可以声明事件的存取器,格式如下:
修饰符 event 委托类型名 事件名
{
add{ ... }
remove{ ... }
}
事件的存取,也就是事件中委托的加入与移除,所对应的存取器就是add
及remove
。在add
及remove
存取器的{}
中,可以写上要完成的任务。当对事件使用 +=
及 -=
运算符时,实际上就是调用事件存取器的add
与remove
方法。
事实上,如果在声明事件时,如果没有声明事件存取器,编译器会自动产生一个,其形式如下:
event D e
{
add{ e += value; }
remove{ e -= value; }
}
其中,D
为委托类型名,value
变量的含义与属性中的value
变量相似,表示参数。声明add
及remove
方法,使得事件的处理可以更具个性化。但要注意,对于abstract
事件,不能声明事件存取器。
特别注意的是,如果声明了事件存取器,对于事件的运算符就只能是 +=
及 -=
。