事件概述                                                           

     在发生其他类或对象关注的事情时,类或对象可通过事件通知它们。发送(或引发)事件的类称为“发行者”,接收(或处理)事件的类称为“订户”。
  • 特点
    • 发行者确定何时引发事件,订户确定执行何种操作来响应该事件。
    • 一个事件可以有多个订户。一个订户可处理来自多个发行者的多个事件。
    • 没有订户的事件永远不会被调用。
    • 事件通常用于通知用户操作
    • 如果一个事件有多个订户,当引发该事件时,会同步调用多个事件处理程序,也可以设置异步调用事件。
    • 可以利用事件同步线程。
    • 事件是基于 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 组件提供了一个简单的解决方案。对于更复杂的异步应用程序,请考虑实现一个符合基于事件的异步模式的类。
    • “在后台”执行耗时任务(例如下载和数据库操作),但不会中断您的应用程序。
    • 同时执行多个操作,每个操作完成时都会接到通知。
    • 等待资源变得可用,但不会停止(“挂起”)您的应用程序。
    • 使用熟悉的事件和委托模型与挂起的异步操作通信。

.NET 事件的更多相关文章

  1. JNI详解---从不懂到理解

    转载:https://blog.csdn.net/hui12581/article/details/44832651 Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... 11 C ...

  2. Jquery的点击事件,三句代码完成全选事件

    先来看一下Js和Jquery的点击事件 举两个简单的例子 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&q ...

  3. 关于 Chrome 浏览器中 onresize 事件的 Bug

    我在写插件时用到了 onresize 事件,在反复地测试后发现该事件在 Chrome 及 Opera(内核基本与 Chrome 相同,以下统称 Chrome)浏览器打开时就会执行,这种情况也许不能算作 ...

  4. MVVM设计模式和WPF中的实现(四)事件绑定

    MVVM设计模式和在WPF中的实现(四) 事件绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  5. C++中的事件分发

    本文意在展现一个C++实现的通用事件分发系统,能够灵活的处理各种事件.对于事件处理函数的注册,希望既能注册到普通函数,注册到事件处理类,也能注册到任意类的成员函数.这样在游戏客户端的逻辑处理中,可以非 ...

  6. 移动端IOS点击事件失效解决方案

    解决方案 解决办法有 4 种可供选择: 1 将 click 事件直接绑定到目标元素(即 .target)上 2 将目标元素换成 <a> 或者 button 等可点击的元素 3 将 clic ...

  7. Android笔记——Button点击事件几种写法

    Button点击事件:大概可以分为以下几种: 匿名内部类 定义内部类,实现OnClickListener接口 定义的构造方法 用Activity实现OnClickListener接口 指定Button ...

  8. HTML 事件(一) 事件的介绍

    本篇主要介绍HTML中的事件知识:事件相关术语.DOM事件规范.事件对象. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三 ...

  9. HTML 事件(二) 事件的注册与注销

    本篇主要介绍HTML元素事件的注册.注销的方式. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三) 事件流.事件委托 4. ...

  10. HTML 事件(三) 事件流与事件委托

    本篇主要介绍HTML DOM中的事件流和事件委托. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三) 事件流与事件委托 4 ...

随机推荐

  1. Eclipse添加JPDL4 Schema校验

    由于jbpm官方提供的图形化流程设计器(GPD)功能并不是特别的全面,很多设计并不能全在图形界面下完成.因此,在很多情况下,我们需要直接编辑JPDL的XML源代码,所以, 最好为JPDL XML指定S ...

  2. 如何通过ShadowSocket自动更新Chrome

    经常收到Chrome的更新提示,并下载更新程序后,报无法连接网络,然后更新不能了, 经过一段时间的搜索,找到了一条比较好的方法,分享一下: 1. 本机打开ShadowSocket 2. 打开Privo ...

  3. js兼容获取元素的样式

    js获取元素的样式的兼容性处理: function getStyle(obj,attr){ return obj.currentStyle?obj.currentStyle[attr]:getComp ...

  4. Ubuntu(基于Ubuntu)中常用的apt和dpkt命令

    apt-get sudo apt-get install package 安装包   sudo apt-get -f install 修复安装”-f = ——fix-missing”   sudo a ...

  5. 关于IE8兼容svg图片问题解决

    参考 http://www.zhangxinxu.com/wordpress/2013/09/svg-fallbacks/ 对博主表示感谢,详述请参考上述链接 直接把代码复制过来了,备份记录下 < ...

  6. 【bzoj4517】 Sdoi2016—排列计数

    http://www.lydsy.com/JudgeOnline/problem.php?id=4517 (题目链接) 题意 求n个数中正好m个数位置不变的排列数. Solution $${错排公式: ...

  7. java连接mysql

    Java 连接 MySQL 需要驱动包,最新版下载地址为:http://dev.mysql.com/downloads/connector/j/,解压后得到jar库文件,然后在对应的项目中导入该库文件 ...

  8. NodeJS:Error: Cannot find module 'jshint/src/cli'

    以前命令:npm install gulp-jshint --save-dev 实质上是安装jshint失败,缺少该模块. 更换命令 :npm install --save-dev jshint gu ...

  9. Finite State Machine 是什么?

    状态机(Finite State Machine):状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动       作.完成特定操作的控制中心. 类 ...

  10. ajax之 get post请求

    get请求 function get(){ $.get( "./Aservlet?id=5", function(data, textStatus, jqXHR){ $(" ...