事件概述
在发生其他类或对象关注的事情时,类或对象可通过事件通知它们。发送(或引发)事件的类称为“发行者”,接收(或处理)事件的类称为“订户”。
- 特点
- 发行者确定何时引发事件,订户确定执行何种操作来响应该事件。
- 一个事件可以有多个订户。一个订户可处理来自多个发行者的多个事件。
- 没有订户的事件永远不会被调用。
- 事件通常用于通知用户操作
- 如果一个事件有多个订户,当引发该事件时,会同步调用多个事件处理程序,也可以设置异步调用事件。
- 可以利用事件同步线程。
- 事件是基于 EventHandler 委托和 EventArgs 基类的。
事件的订阅和取消订阅
如果您想编写引发事件时调用的自定义代码,则可以订阅由其他类发布的事件。例如,可以订阅某个按钮的“单击”事件,以使应用程序在用户单击该按钮时执行一些有用的操作。
- 订阅事件
- VS IDE 订阅事件
- 如果“属性”窗口不可见,请在“设计”视图中,右击要创建事件处理程序的窗体或控件,然后选择“属性”。
- 在“属性”窗口的顶部,单击“事件”图标。
- 双击要创建的事件,Visual C# 会创建一个空事件处理程序方法,并将其添加到您的代码中。或者,您也可以在“代码”视图中手动添加代码。
- 编程方式订阅事件
- 定义一个事件处理程序方法,其签名与该事件的委托签名匹配。例如,如果事件基于 EventHandler 委托类型,则下面的代码表示方法存根
void HandleCustomEvent(object sender, CustomEventArgs a){ }
|
- 使用加法赋值运算符 (+=) 来为事件附加事件处理程序。在下面的示例中,假设名为 publisher 的对象拥有一个名为 RaiseCustomEvent 的事件。请注意,订户类需要引用发行者类才能订阅其事件。
publisher.RaiseCustomEvent += HandleCustomEvent;
publisher.RaiseCustomEvent += new CustomEventHandler(HandleCustomEvent);
|
- 匿名方法订阅事件
- 使用加法赋值运算符 (+=) 来为事件附加匿名方法。在下面的示例中,假设名为 publisher 的对象拥有一个名为 RaiseCustomEvent 的事件,并且还定义了一个 CustomEventArgs 类以承载某些类型的专用事件信息。请注意,订户类需要引用 publisher 才能订阅其事件。
publisher.RaiseCustomEvent += delegate(object o, CustomEventArgs e)
{
string s = o.ToString() + " " + e.ToString();
Console.WriteLine(s);
};
|
要防止在引发事件时调用事件处理程序,您只需取消订阅该事件。要防止资源泄露,请在释放订户对象之前取消订阅事件,这一点很重要。在取消订阅事件之前,在发布对象中作为该事件的基础的多路广播委托会引用封装了订户的事件处理程序的委托。只要发布对象包含该引用,就不会对订户对象执行垃圾回收。
使用减法赋值运算符 (-=) 取消订阅事件。所有订户都取消订阅某事件后,发行者类中的事件实例会设置为 null。
publisher.RaiseCustomEvent -= HandleCustomEvent;
|
发布标准事件
下面的过程演示了如何将符合标准 .NET Framework 模式的事件添加到您自己的类和结构中。.NET Framework 类库中的所有事件均基于 EventHandler 委托,定义如下。
public delegate void EventHandler(object sender, EventArgs e);
|
- 采用 EventHandler 模式发布事件
- (如果不需要发送含事件的自定义数据,请跳过此步骤,直接进入步骤 3。)在发行者类和订户类均可看见的范围中声明类,并添加保留自定义事件数据所需的成员。在此示例中,会返回一个简单字符串。
public class CustomEventArgs : EventArgs
{
public CustomEventArgs(string s)
{
msg = s;
}
private string msg;
public string Message
{
get { return msg; }
}
}
|
- (如果您使用的是 EventHandler 的泛型版本,请跳过此步骤。)在发布类中声明一个委托。为它指定以 EventHandler 结尾的名称。第二个参数指定自定义 EventArgs 类型。
public delegate void CustomEventHandler(object sender, CustomEventArgs a);
|
- 使用以下任一步骤,在发布类中声明事件。
- 如果没有自定义 EventArgs 类,事件类型就是非泛型 EventHandler 委托。它无需声明,因为它已在 C# 项目默认包含的 System 命名空间中进行了声明
public event EventHandler RaiseCustomEvent;
|
- 如果使用的是 EventHandler 的非泛型版本,并且您有一个由 EventArgs 派生的自定义类,请在发布类中声明您的事件,并且将您的委托用作类型
class Publisher
{
public event CustomEventHandler RaiseCustomEvent;
}
|
- 如果使用的是泛型版本,则不需要自定义委托。相反,应将事件类型指定为 EventHandler<CustomEventArgs>,在尖括号内放置您自己的类的名称。
public event EventHandler<CustomEventArgs> RaiseCustomEvent;
|
引发派生类中的基类事件
以下简单示例演示了在基类中声明可从派生类引发的事件的标准方法。此模式广泛应用于 .NET Framework 基类库中的 Windows 窗体类。
在创建可用作其他类的基类的类时,必须考虑如下事实:事件是特殊类型的委托,只可以从声明它们的类中调用。派生类无法直接调用基类中声明的事件。尽管有时您可能希望某个事件只能通过基类引发,但在大多数情形下,您应该允许派生类调用基类事件。为此,您可以在包含该事件的基类中创建一个受保护的调用方法。通过调用或重写此调用方法,派生类便可以间接调用该事件。
namespace BaseClassEvents
{
using System;
using System.Collections.Generic;
public class ShapeEventArgs : EventArgs
{
private double newArea;
public ShapeEventArgs(double d)
{
newArea = d;
}
public double NewArea
{
get { return newArea; }
}
}
public abstract class Shape
{
protected double area;
public double Area
{
get { return area; }
set { area = value; }
}
public event EventHandler<ShapeEventArgs> ShapeChanged;
public abstract void Draw();
protected virtual void OnShapeChanged(ShapeEventArgs e)
{
EventHandler<ShapeEventArgs> handler = ShapeChanged;
if (handler != null)
{
handler(this, e);
}
}
}
public class Circle : Shape
{
private double radius;
public Circle(double d)
{
radius = d;
area = 3.14 * radius;
}
public void Update(double d)
{
radius = d;
area = 3.14 * radius;
OnShapeChanged(new ShapeEventArgs(area));
}
protected override void OnShapeChanged(ShapeEventArgs e)
{
base.OnShapeChanged(e);
}
public override void Draw()
{
Console.WriteLine("Drawing a circle");
}
}
public class Rectangle : Shape
{
private double length;
private double width;
public Rectangle(double length, double width)
{
this.length = length;
this.width = width;
area = length * width;
}
public void Update(double length, double width)
{
this.length = length;
this.width = width;
area = length * width;
OnShapeChanged(new ShapeEventArgs(area));
}
protected override void OnShapeChanged(ShapeEventArgs e)
{
base.OnShapeChanged(e);
}
public override void Draw()
{
Console.WriteLine("Drawing a rectangle");
}
}
public class ShapeContainer
{
List<Shape> _list;
public ShapeContainer()
{
_list = new List<Shape>();
}
public void AddShape(Shape s)
{
_list.Add(s);
s.ShapeChanged += HandleShapeChanged;
}
private void HandleShapeChanged(object sender, ShapeEventArgs e)
{
Shape s = (Shape)sender;
Console.WriteLine("Received event. Shape area is now {0}", e.NewArea);
s.Draw();
}
}
class Test
{
static void Main(string[] args)
{
Circle c1 = new Circle(54);
Rectangle r1 = new Rectangle(12, 9);
ShapeContainer sc = new ShapeContainer();
sc.AddShape(c1);
sc.AddShape(r1);
c1.Update(57);
r1.Update(7, 7);
Console.WriteLine();
Console.WriteLine("Press Enter to exit");
Console.ReadLine();
}
}
}
|
实现接口事件
接口可声明事件。下面的示例演示如何在类中实现接口事件。接口事件的实现规则与任何接口方法或属性的实现规则基本相同。
public interface IDrawingObject
{
event EventHandler ShapeChanged;
}
public class MyEventArgs : EventArgs {…}
public class Shape : IDrawingObject
{
event EventHandler ShapeChanged;
void ChangeShape()
{
// Do something before the event…
OnShapeChanged(new MyEventsArgs(…));
// or do something after the event.
}
protected virtual void OnShapeChanged(MyEventArgs e)
{
if(ShapeChanged != null)
{
ShapeChanged(this, e);
}
}
}
|
下面的示例演示如何处理以下的不常见情况:您的类是从两个以上的接口继承的,每个接口都含有同名事件)。在这种情况下,您至少要为其中一个事件提供显式接口实现。为事件编写显式接口实现时,必须编写 add 和 remove 事件访问器。这两个事件访问器通常由编译器提供,但在这种情况下编译器不能提供。
您可以提供自己的访问器,以便指定这两个事件是由您的类中的同一事件表示,还是由不同事件表示。例如,根据接口规范,如果事件应在不同时间引发,则可以将每个事件与类中的一个单独实现关联。在下面的示例中,订户将形状引用强制转换为 IShape 或 IDrawingObject,从而确定自己将会接收哪个 OnDraw 事件。
namespace WrapTwoInterfaceEvents
{
using System;
public interface IDrawingObject
{
event EventHandler OnDraw;
}
public interface IShape
{
event EventHandler OnDraw;
}
public class Shape : IDrawingObject, IShape
{
event EventHandler PreDrawEvent;
event EventHandler PostDrawEvent;
event EventHandler IDrawingObject.OnDraw
{
add { PreDrawEvent += value; }
remove { PreDrawEvent -= value; }
}
event EventHandler IShape.OnDraw
{
add { PostDrawEvent += value; }
remove { PostDrawEvent -= value; }
}
public void Draw()
{
EventHandler handler = PreDrawEvent;
if (handler != null)
{
handler(this, new EventArgs());
}
Console.WriteLine("Drawing a shape.");
handler = PostDrawEvent;
if (handler != null)
{
handler(this, new EventArgs());
}
}
}
public class Subscriber1
{
public Subscriber1(Shape shape)
{
IDrawingObject d = (IDrawingObject)shape;
d.OnDraw += new EventHandler(d_OnDraw);
}
void d_OnDraw(object sender, EventArgs e)
{
Console.WriteLine("Sub1 receives the IDrawingObject event.");
}
}
public class Subscriber2
{
public Subscriber2(Shape shape)
{
IShape d = (IShape)shape;
d.OnDraw += new EventHandler(d_OnDraw);
}
void d_OnDraw(object sender, EventArgs e)
{
Console.WriteLine("Sub2 receives the IShape event.");
}
}
public class Program
{
static void Main(string[] args)
{
Shape shape = new Shape();
Subscriber1 sub = new Subscriber1(shape);
Subscriber2 sub2 = new Subscriber2(shape);
shape.Draw();
Console.WriteLine("Press Enter to close this window.");
Console.ReadLine();
}
}
}
|
使用字典存储事件实例
accessor-declarations 的一种用法是公开大量的事件但不为每个事件分配字段,而是使用字典来存储这些事件实例。这只有在具有非常多的事件、但您预计大部分事件都不会实现时才有用。
public delegate void EventHandler1(int i);
public delegate void EventHandler2(string s);
public class PropertyEventsSample
{
private System.Collections.Generic.Dictionary<string, System.Delegate> eventTable;
public PropertyEventsSample()
{
eventTable = new System.Collections.Generic.Dictionary<string, System.Delegate>();
eventTable.Add("Event1", null);
eventTable.Add("Event2", null);
}
public event EventHandler1 Event1
{
add
{
eventTable["Event1"] = (EventHandler1)eventTable["Event1"] + value;
}
remove
{
eventTable["Event1"] = (EventHandler1)eventTable["Event1"] - value;
}
}
public event EventHandler2 Event2
{
add
{
eventTable["Event2"] = (EventHandler2)eventTable["Event2"] + value;
}
remove
{
eventTable["Event2"] = (EventHandler2)eventTable["Event2"] - value;
}
}
internal void RaiseEvent1(int i)
{
EventHandler1 handler1;
if (null != (handler1 = (EventHandler1)eventTable["Event1"]))
{
handler1(i);
}
}
internal void RaiseEvent2(string s)
{
EventHandler2 handler2;
if (null != (handler2 = (EventHandler2)eventTable["Event2"]))
{
handler2(s);
}
}
}
public class TestClass
{
public static void Delegate1Method(int i)
{
System.Console.WriteLine(i);
}
public static void Delegate2Method(string s)
{
System.Console.WriteLine(s);
}
static void Main()
{
PropertyEventsSample p = new PropertyEventsSample();
p.Event1 += new EventHandler1(TestClass.Delegate1Method);
p.Event1 += new EventHandler1(TestClass.Delegate1Method);
p.Event1 -= new EventHandler1(TestClass.Delegate1Method);
p.RaiseEvent1(2);
p.Event2 += new EventHandler2(TestClass.Delegate2Method);
p.Event2 += new EventHandler2(TestClass.Delegate2Method);
p.Event2 -= new EventHandler2(TestClass.Delegate2Method);
p.RaiseEvent2("TestString");
}
}
|
事件的异步模式
有多种方式可向客户端代码公开异步功能。基于事件的异步模式为类规定了用于显示异步行为的建议方式。对于相对简单的多线程应用程序,BackgroundWorker 组件提供了一个简单的解决方案。对于更复杂的异步应用程序,请考虑实现一个符合基于事件的异步模式的类。
- “在后台”执行耗时任务(例如下载和数据库操作),但不会中断您的应用程序。
- 同时执行多个操作,每个操作完成时都会接到通知。
- 等待资源变得可用,但不会停止(“挂起”)您的应用程序。
- 使用熟悉的事件和委托模型与挂起的异步操作通信。
- JNI详解---从不懂到理解
转载:https://blog.csdn.net/hui12581/article/details/44832651 Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... 11 C ...
- Jquery的点击事件,三句代码完成全选事件
先来看一下Js和Jquery的点击事件 举两个简单的例子 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&q ...
- 关于 Chrome 浏览器中 onresize 事件的 Bug
我在写插件时用到了 onresize 事件,在反复地测试后发现该事件在 Chrome 及 Opera(内核基本与 Chrome 相同,以下统称 Chrome)浏览器打开时就会执行,这种情况也许不能算作 ...
- MVVM设计模式和WPF中的实现(四)事件绑定
MVVM设计模式和在WPF中的实现(四) 事件绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...
- C++中的事件分发
本文意在展现一个C++实现的通用事件分发系统,能够灵活的处理各种事件.对于事件处理函数的注册,希望既能注册到普通函数,注册到事件处理类,也能注册到任意类的成员函数.这样在游戏客户端的逻辑处理中,可以非 ...
- 移动端IOS点击事件失效解决方案
解决方案 解决办法有 4 种可供选择: 1 将 click 事件直接绑定到目标元素(即 .target)上 2 将目标元素换成 <a> 或者 button 等可点击的元素 3 将 clic ...
- Android笔记——Button点击事件几种写法
Button点击事件:大概可以分为以下几种: 匿名内部类 定义内部类,实现OnClickListener接口 定义的构造方法 用Activity实现OnClickListener接口 指定Button ...
- HTML 事件(一) 事件的介绍
本篇主要介绍HTML中的事件知识:事件相关术语.DOM事件规范.事件对象. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三 ...
- HTML 事件(二) 事件的注册与注销
本篇主要介绍HTML元素事件的注册.注销的方式. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三) 事件流.事件委托 4. ...
- HTML 事件(三) 事件流与事件委托
本篇主要介绍HTML DOM中的事件流和事件委托. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三) 事件流与事件委托 4 ...
随机推荐
- 备忘:spring jdbc事务代码 mybatis, nhibernate
http://files.cnblogs.com/files/mikelij/mymavenMar1.rar
- RapidJSON 代码剖析(四):优化 Grisu
我曾经在知乎的一个答案里谈及到 V8 引擎里实现了 Grisu 算法,我先引用该文的内容简单介绍 Grisu.然后,再谈及 RapidJSON 对它做了的几个底层优化. (配图中的<Grisù& ...
- APIPA(Automatic Private IP Addressing,自动专用IP寻址)
APIPA APIPA(Automatic Private IP Addressing,自动专用IP寻址),是一个DHCP故障转移机制.当DHCP服务器出故障时, APIPA在169.254.0.1到 ...
- 【SQL】sql server 2008R2 评估期已过,
参考1:http://www.cnblogs.com 参考2:http://www.wang1314.com 个人认为:升级+秘钥,,买正版才是最终的解决方法.
- 不可错过的javascript迷你库
最近看着下自己的github star,把我吓坏了,手贱党,收藏癖的我都收藏了300+个仓库了,是时候整理一下了. Unix主张kiss,小而美被实践是最好用的,本文将介绍笔者收集的一些非常赞的开源库 ...
- bzoj2194: 快速傅立叶之二
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #i ...
- node 常用命令
nvm nvm list 列出安装的node npm install -g cnpm --registry=https://registry.npm.taobao.org 安装cnpm npm i ...
- mybatis一个怪异的问题: Invalid bound statement not found
ssm中报一下错误: invalid bound statement (not found): me.tspace.pm.dao.userdao.getuser at org.apache.ib ...
- oracle中将自建用户下的所有表删除
select 'drop table '||table_name||' ;' from user_tables;select 'drop sequence '||sequence_name||' ;' ...
- RabbitMQ消息队列(一): Detailed Introduction 详细介绍
http://blog.csdn.net/anzhsoft/article/details/19563091 RabbitMQ消息队列(一): Detailed Introduction 详细介绍 ...