理解C#中的事件

前面文章中介绍了委托相关的概念,委托实例保存这一个或一组操作,程序中将在某个特定的时刻通过委托实例使用这些操作。

如果做过GUI程序开发,可能对上面的描述会比较熟悉。在GUI程序中,单击一个button会触发一个click事件,然后会执行一系列的操作,这一系列的操作就被存放在一个委托实例中。

接下来我们就看看事件。

使用委托中的问题

回到前面文章中苹果和富士康的例子,苹果将iphone的组装、包装和运输的工作全部委托给了富士康。

根据上面的描述,我们修改了一下代码,在Apple这个类中加入一个订单属性,苹果只要接到新的订单,就发送一个通知给富士康,然后富士康就会执行一系列的操作了(组装、包装和运输)。

在主程序中,苹果将iphone的组装、包装和运输工作委托给了富士康,然后苹果每次收到订单,就会通过委托实例"VerdorToAssembleIphone"让富士康去执行一系列操作。

代码如下:

class Apple
{
    //声明委托类型
    public delegate void AssembleIphoneHandler(int num);
    public AssembleIphoneHandler VerdorToAssembleIphone;

public void DesignIphone()
    {
        Console.WriteLine("Design Iphone By Apple");
    }

private int orderNum;
    public int OrderNum
    {
        get { return this.orderNum; }
        set
        {
            this.orderNum = value;
            if (VerdorToAssembleIphone != null)
            {
                VerdorToAssembleIphone(this.orderNum);
            }
        }
    }
}

class Foxconn
{
    //与委托类型签名相同的方法
    public void AssembleIphone(int num)
    {
        Console.WriteLine("Assemble {0} Iphone By Foxconn", num);
    }

public void PackIphone(int num)
    {
        Console.WriteLine("Pack {0} Ipnone By Foxconn", num);
    }

public void ShipIphone(int num)
    {
        Console.WriteLine("Ship {0} Iphone By Foxconn", num);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Apple apple = new Apple();
        Foxconn foxconn = new Foxconn();
        apple.VerdorToAssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone);
        apple.VerdorToAssembleIphone += new Apple.AssembleIphoneHandler(foxconn.PackIphone);
        apple.VerdorToAssembleIphone += new Apple.AssembleIphoneHandler(foxconn.ShipIphone);

apple.OrderNum = 100;
    
        Console.Read();
    }
}

下面我们看下这个例子实现中的问题:

如果用户在建立委托链的时候错误的使用了"="而不是"+=",那么委托链就断了
在主程序中,我们可以绕过设置订单这一步,直接调用委托实例"apple.VerdorToAssembleIphone(99);"来让富士康执行操作,这点是不合理的

代码如下:

class Program
{
    static void Main(string[] args)
    {
        Apple apple = new Apple();
        Foxconn foxconn = new Foxconn();

//创建委托实例
        apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone);
        apple.AssembleIphone += new Apple.AssembleIphoneHandler(foxconn.PackIphone);
        apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.ShipIphone);
        apple.DesignIphone();
       
        //委托实例的调用
        apple.VerdorToAssembleIphone(99);
    
        Console.Read();
    }
}

事件的出现

为了解决上面两个问题,出现了事件这个概念,我们要做的改变只是在声明委托实例的时候加上event关键字。

代码如下:

public event AssembleIphoneHandler VerdorToAssembleIphone;

这时,上面两处有问题的代码就会在编译的时候报错了。

上面的问题是解决了,但是event关键字作用是什么,事件跟委托有什么关系,"VerdorToAssembleIphone"怎么理解?

深入理解事件

其实,下面这个语句还是比较难理解的,看到的第一反应就是,委托跟事件到底是什么关系,event关键字跟委托类型"AssembleIphoneHandler"是什么关系。

代码如下:

public event AssembleIphoneHandler VerdorToAssembleIphone;

其实,事件可以理解成一个委托的属性,通过对委托实例的封装来对委托实例的访问进行一些限制。下面我们通过IL来查看一下这个个程序,得到下图。

下面我们就结合IL的查看结果来分析事件到底是什么。在开始之前,相信大家一定都熟悉属性(property)这个概念吧,那就让我们从熟悉的属性开始分析。

C#属性的概念

首先来看看我们熟悉的东西,"OrderNum"是我们定义的一个订单数量的属性(property),它有一组get/set方法。通过这个属性的get/set方法,我们可以访问"orderNum"字段(field)。

通过IL查看"OrderNum"这个属性,我们可以看到这个属性对应的get/set方法。

代码如下:

public int32 get_OrderNum() { }
public void set_OrderNum(int32 'value') { }

事件

接下来再看"VerdorToAssembleIphone"事件,我们按照属性的方式去理解事件,从IL的截图中,可以看到事件中有一对addon/removeon方法来操作我们的委托实例(想想属性的get/set方法)。

同时,我们看到编译器给我们生成了一个private的field(如下),从这里我们可以看到事件"VerdorToAssembleIphone"本质上就是一个委托类型的变量。由于这个变量是private的,也就解释了为什么我们在定义事件的类外部不能直接访问这个变量。

代码如下:

.field private class _1_1_Delegate.Apple/AssembleIphoneHandler VerdorToAssembleIphone

事件代码的转换
根据上面的分析,我们可以看到,编译器帮我们进行了下面的代码转换。这样一来,就相当于通过event关键字对委托实例访问增加了一些限制。

原始的声明事件的C#代码:

代码如下:

public event AssembleIphoneHandler VerdorToAssembleIphone;

编译器转换后的代码:

代码如下:

private AssembleIphoneHandler VerdorToAssembleIphone;

public void add_VerdorToAssembleIphone(AssembleIphoneHandler 'value') { }

public void remove_VerdorToAssembleIphone(AssembleIphoneHandler 'value') { }

通过上面的分析,可以了解到事件封装了委托类型的实例,使得:

1.在定义事件的类内部,不管你声明它是public还是protected,它总是private的;也就是说在在定义事件的类外部不能对事件进行调用
2.在定义事件的类外部,添加"+="和移除"-="委托实例的访问限定符与声明事件时使用的访问符相同
2-1).也就是说,如果事件声明为private或这protect, 那么定义事件的类外部也不能对事件进行"+="和"-="操作

事件编程

其实,上面的例子只是一个简单的演示。在很多情况下,事件使用过程中都会结合两个参数:事件源和事件参数。

所以,在事件编程中,可以参考下面一些规范:

1.统一以EventNameEventHandler方式命名委托变量:EventName是事件的名称
2.delegate接受两个参数,参数名统一命名为sender和e:第一个参数类型是object,第二个参数是事件参数类型,以EventNameEventArgs命名,并且需继承于System.EventArgs类
3.如果在事件中不需要传递任何数据,也需要声明两个参数:第一个参数就是默认的object sender,第二个参数可以使用系统默认的System.EventArgs类

总结

通过本文介绍了事件的概念以及原理,解释了通过事件如何封装委托实例,并解决委托例子中遇到的两个问题。

同时了解了事件的使用:

通过event关键字声明事件,

代码如下:

<事件修饰> event <委托类型> <事件名称>;

事件的调用,由于事件本质上是委托类型,调用事件与调用委托一样,但是事件的调用只能发生在定义事件的类的内部

(0)

相关推荐

  • C#自定义事件及用法实例

    本文实例讲述了C#自定义事件及用法.分享给大家供大家参考.具体分析如下: 事件是C#中一个重要的内容,MSDN上有一个自定义事件的演示示例.我看了半天有点晕,所以新建了一个winform工程添加了一个按钮,然后找出调用的程序,一对比做了一个类似的示例,就明白了.看代码有时候比看文档来得更快. 所以还是一贯的原则,来干的,不来稀的. using System; namespace TestEventArgs { /// <summary> /// 这个类对应于EventArgs,做对比学习. /

  • C#委托与事件初探

    委托给了C#操作函数的灵活性,我们可使用委托像操作变量一样来操作函数,其实这个功能并不是C#的首创,早在C++时代就有函数指针这一说法,而在我看来委托就是C#的函数指针,首先先简要的介绍一下委托的基本知识: 委托的定义 委托的声明原型是 delegate <函数返回类型> <委托名> (<函数参数>) 例子:public delegate void CheckDelegate(int number);//定义了一个委托CheckDelegate,它可以注册返回void类

  • 在C#使用字典存储事件示例及实现自定义事件访问器

    使用字典存储事件实例 accessor-declarations 的一种用法是公开很多事件但不为每个事件分配字段,而是使用字典来存储这些事件实例.这只在具有很多事件但您预计大多数事件都不会实现时才有用. public delegate void EventHandler1(int i); public delegate void EventHandler2(string s); public class PropertyEventsSample { private System.Collecti

  • C#通过委托调用Button单击事件的方法

    这里介绍通过委托取消Button事件switch-case的方法.需要注意的是,事先要按顺序在各个Button的Tag属性中设置0.1.2.3--等序号,其作用请详看代码. /*定义委托*/ public delegate 类型或viod MethodDelegate(参数1, 参数2); private void buttonC_Click(object sender, EventArgs e) { Button button = (Button)sender; /*向委托添加方法*/ Met

  • C#移除所有事件绑定的方法

    本文实例讲述了C#移除所有事件绑定的方法.分享给大家供大家参考.具体分析如下: private delegate int DEL_TEST_EventHandler(int m, int n); private event DEL_TEST_EventHandler DelTestEventHandler; /// <summary> /// 移除所有的事件绑定 /// </summary> /// <param name="clearEvent">

  • 结合.net框架在C#派生类中触发基类事件及实现接口事件

    在派生类中引发基类事件 以下简单示例演示了在基类中声明可从派生类引发的事件的标准方法.此模式广泛应用于 .NET Framework 类库中的 Windows 窗体类. 在创建可用作其他类的基类的类时,应考虑如下事实:事件是特殊类型的委托,只可以从声明它们的类中调用.派生类无法直接调用基类中声明的事件.尽管有时需要事件仅由基类引发,但在大多数情形下,应该允许派生类调用基类事件.为此,您可以在包含该事件的基类中创建一个受保护的调用方法.通过调用或重写此调用方法,派生类便可以间接调用该事件. 注意:

  • C#动态生成按钮及定义按钮事件的方法

    本文实例讲述了C#动态生成按钮及定义按钮事件的方法.分享给大家供大家参考.具体实现方法如下: 1.后台生成input的button按钮 复制代码 代码如下: HtmlGenericControl control = new HtmlGenericControl("input"); control.Attributes.Add("type", "button"); control.Attributes.Add("onclick"

  • 结合Visual C#开发环境讲解C#中事件的订阅和取消订阅

    类或对象可以通过事件向其他类或对象通知发生的相关事情.发送(或引发)事件的类称为"发行者",接收(或处理)事件的类称为"订户". 在典型的 C# Windows 窗体或 Web 应用程序中,可订阅由控件(如按钮和列表框)引发的事件.可使用 Visual C# 集成开发环境 (IDE) 来浏览控件发布的事件,选择要处理的事件.IDE 会自动添加空事件处理程序方法和订阅事件的代码. 事件概述 事件具有以下特点: 发行者确定何时引发事件,订户确定执行何种操作来响应该事件.

  • C#中事件的定义和使用

    事件的声明和使用与代理有很密切的关系,事件其实是一个或多个方法的代理,当对象的某个状态发生了变化,代理会被自动调用,从而代理的方法就被自动执行. 声明和使用一个事件需要如下步骤: 1.创建一个代理. 2.在类的内部利用event关键字声明事件,并且在类中定义调用事件的方法,也可以定义一个处理事件消息的方法. 声明一个事件的基本形式有两种: 修饰符  event   类型   标识符 修饰符  event   类型   标识符{get{};set{};} 其中: 修饰符是指C#语言的访问修饰符:类

  • 详解C#编程中.NET的弱事件模式

    引言 你可能知道,事件处理是内存泄漏的一个常见来源,它由不再使用的对象存留产生,你也许认为它们应该已经被回收了,但不是,并有充分的理由. 在这个短文中(期望如此),我会在 .Net 框架的上下文事件处理中展示这个问题,之后我会教你这个问题的标准解决方案,弱事件模式.有两种方法,即: "传统"方法 (嗯,在 .Net 4.5 前,所以也没那么老),它实现起来比较繁琐 .Net 4.5 框架提供的新方法,它则是尽其可能的简单 (源代码在 这里 可供使用.) 从常见事物开始 在一头扎进本文核

  • C#获取USB事件API实例分析

    本文实例讲述了C#获取USB事件API.分享给大家供大家参考.具体如下: const int WM_DEVICECHANGE = 0x2190; const int DBT_DEVICEARRIVAL = 0x8000; const int DBT_DEVICEREMOVECOMPLETE = 0x8004; protected override void WndProc(ref Message m) { try { //if (m.Msg == WM_DEVICECHANGE) //{ swi

  • C#自定义事件监听实现方法

    本文实例讲述了C#自定义事件监听实现方法.分享给大家供大家参考.具体实现方法如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApp { /// <summary> /// 定义事件 /// </summary> class CustomEvent { /// <summary> /// 定义委托 /// &

  • C#事件用法实例浅析

    本文实例讲述了C#事件用法.分享给大家供大家参考.具体分析如下: EventHandler<TEventArgs>的定义如下 public delegate void EventHandler<TEventArgs>(object sender,TEventArgs e) where TEventArgs:EventArgs 第一个参数必须是object类型(是一个对象,包含事件的发送者) 第二个参数是T类型(即泛型),定义了一个T的约束,它必须派生自基类EventArgs Car

随机推荐