C#事件

事件

事件含义

事件由对象引发,通过我们提供的代码来处理。一个事件我们必须订阅(Subscribe)他们,订阅一个事件的含义就是提供代码,在这个事件发生时执行这些代码,这些代码称为事件处理程序
一个事件可以被多个事件处理程序订阅,在这个事件发生时,这些处理程序都会被执行。事件处理程序可以在该事件的对象所处的类中,也可以在其他类中。
事件处理程序本身就是一个普通的方法,对这个方法的唯一限制是:必须匹配事件所要求的返回类型和参数,这个限制是事件定义的一部分,由一个委托指定。

处理事件

要处理事件,需要提供一个事件处理方法来订阅事件,方法的返回类型和参数必须匹配事件指定的委托。下面使用一个简单的计时器对象来引发事件,调用一个处理方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

namespace eve
{
    class Program
    {
        static int counter = 0;

        static string displayString =
           "This string will appear one letter at a time. ";

        static void Main(string[] args)
        {
            Timer myTimer = new Timer(100);
            myTimer.Elapsed += WriteChar;
            // myTimer.Elapsed += WriteChar2;
            //myTimer.Elapsed += new ElapsedEventHandler(WriteChar);
          //  myTimer.Elapsed += (sender, eventArgs) =>
           // {
           //     Console.Write(displayString[counter++ % displayString.Length]);
          //  };
            myTimer.Start();
            System.Threading.Thread.Sleep(2000);
            Console.ReadKey();
        }

        static void WriteChar(object source, ElapsedEventArgs e)
        {
            Console.Write(displayString[counter++ % displayString.Length]);
        }
        static void WriteChar2(object source, ElapsedEventArgs e)
        {
            Console.Write(counter);
        }
    }
}

这个示例运行后将会打印字符,一个一个打印。那么我来解释一下上面的栗子。

Timer 是一个定时器,每隔一段时间会触发一个事件,这个时间在构造函数里给出,这里是100ms。要引发事件,首先要把这个定时器跑起来,就是myTimer.Start();
这个Timer呢有一个事件叫做Elapsed,我们就要用一个方法去订阅它。条件就是必须匹配System.Timers.ElapsedEventHandler这个委托类型的返回类型和参数。这个委托来自.Net Framework标准委托。它指定了如下的返回类型和参数。
void <MethodName> (Object source, ElapsedEventArgs e);
其中的source参数是Timer对象本身的引用,ElapsedEventArgs是对象的一个实例。后面继续介绍。
在上面给出的代码中有满足这个委托返回类型和参数列表的方法

static void WriteChar(object source, ElapsedEventArgs e)
{
        Console.Write(displayString[counter++ % displayString.Length]);

}

先不管后面那个WriteChar2。
里面的方法在屏幕上一次输出字符串中的字符。
好了,事件有了,对应的方法有了,剩下的工作就是去订阅这个事件。
使用`+=`运算符,给事件添加一个处理程序,形式是使用事件处理方法初始化一个新的**委托实例**。但是方法不止一种:
1. `myTimer.Elapsed += new ElapsedEventHandler(WriteChar);`
2. `Timer.Elapsed += WriteChar;`
上面两个方式都是可以的,使用第2种编译器会根据使用的上下文来指定委托类型。坏处就是降低可读性,你不能一下就知道这个委托类型是什么。
其实还有方法:
**Lambda表达式**

myTimer.Elapsed += (sender, eventArgs) =>
{
Console.Write(displayString[counter++ % displayString.Length]);
};

或者 **匿名方法**

myTimer.Elapsed += delegate(object sender, MessageArrivedEventArgs eventArgs)
{
Console.Write(displayString[counter++ % displayString.Length]);
};

上面的`(object sender, MessageArrivedEventArgs eventArgs)`也是可以省略的。

至此,所有的工作都完成啦。
再理一遍:定时器每隔100ms引发一个事件,这个事件被我们的方法订阅,事件触发后去找那个订阅的处理程序,执行那个方法,在屏幕上输出内容。

创建事件

上面使用了Timer自带的事件,我们也可以创建我们自己的事件。这里是一个即时消息传送程序,创建一个Connection对象,这个对象引发由Display对象处理的事件。
首先定义一个委托类型,你一定知道用它干嘛。
public delegate void MessageHandler(string messageText);

这个委托类型称为MessageHandler,有一个void的方法签名,有一个string 参数。

Connection这个类里定义一个事件

public event MessageHandler MessageArrived

给事件命名,这里使用MessageArrived,在声明时,使用event关键字,并指定要使用的委托类型MessageHandler。这样声明以后,就可以引发它。方法是按名称来调用它。例如:
MessageArrived("This is a message.");
为什么是这样呢?
MessageArrived这是一个委托实例,按照委托的方法传参。
要是定义的委托不需要参数,那就这样MessageArrived();
要是参数多就要用更多的代码区实现。

先上代码吧。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

namespace eve
{
   public delegate void MessageHandler(string messageText);

   public class Connection
   {
      public event MessageHandler MessageArrived;
      private Timer pollTimer;

      public Connection()
      {
         pollTimer = new Timer(100);
         pollTimer.Elapsed += new ElapsedEventHandler(CheckForMessage);
      }

      public void Connect()
      {
         pollTimer.Start();
      }

      public void Disconnect()
      {
         pollTimer.Stop();
      }

      private static Random random = new Random();

      private void CheckForMessage(object source, ElapsedEventArgs e)
      {
         Console.WriteLine("Checking for new messages.");
         if ((random.Next(9) == 0) && (MessageArrived != null))
         {
            MessageArrived("Hello Mum!");
         }
      }
   }
}

if ((random.Next(9) == 0) && (MessageArrived != null))这里得到一个随机数,得到0时引发事件,后面的MessageArrived!=null检查事件是否有被订阅,如果没有被订阅那么MessageArrived=null不会引发事件。

下面是Display

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace eve
{
   public class Display
   {
      public void DisplayMessage(string message)
      {
         Console.WriteLine("Message arrived: {0}", message);
      }
   }
}

其中的public void DisplayMessage(string message)满足上面定义的委托返回类型和参数,可以用它来订阅事件MessageArrived

主程序是这样的

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace eve
{
   class Program
   {
      static void Main(string[] args)
      {
         Connection myConnection = new Connection();
         Display myDisplay = new Display();
         myConnection.MessageArrived +=
                 new MessageHandler(myDisplay.DisplayMessage);
         myConnection.Connect();
         System.Threading.Thread.Sleep(200);
         Console.ReadKey();
      }
   }
}

下面是运行结果

运行结果

多用途的事件处理程序

前面使用的Timer.Elapsed事件的委托包含了事件处理程序中常见的两类参数

  • object source引发事件的对象的引用
  • ElapsedEventArgs e 由事件传送的参数
    在事件中使用object类型的原因是,我们常常要为由不同对象引发的几个相同事件使用同一个事件处理程序,但仍要指定是哪个对象生成了事件。
    下面扩展上面的示例。
    添加一个新类MessageArrivedEventArgs
 public class MessageArrivedEventArgs : EventArgs
   {
      private string message;

      public string Message
      {
         get
         {
            return message;
         }
      }

      public MessageArrivedEventArgs()
      {
         message = "No message sent.";
      }

      public MessageArrivedEventArgs(string newMessage)
      {
         message = newMessage;
      }
   }

修改Connection

public class Connection
   {
      public event EventHandler<MessageArrivedEventArgs> MessageArrived;
      private Timer pollTimer;

      public string Name { get; set; }

      public Connection()
      {
         pollTimer = new Timer(100);
         pollTimer.Elapsed += new ElapsedEventHandler(CheckForMessage);
      }

      public void Connect()
      {
         pollTimer.Start();
      }

      public void Disconnect()
      {
         pollTimer.Stop();
      }

      private static Random random = new Random();

      private void CheckForMessage(object source, ElapsedEventArgs e)
      {
         Console.WriteLine("Checking for new messages.");
         if ((random.Next(9) == 0) && (MessageArrived != null))
         {
            MessageArrived(this, new MessageArrivedEventArgs("Hello Mum!"));
         }
      }
   }

修改Display

public class Display
   {
      public void DisplayMessage(object source, MessageArrivedEventArgs e)
      {
         Console.WriteLine("Message arrived from: {0}",
                           ((Connection)source).Name);
         Console.WriteLine("Message Text: {0}", e.Message);
      }
   }

主程序修改为

 static void Main(string[] args)
      {
         Connection myConnection1 = new Connection();
         myConnection1.Name = "First connection.";
         Connection myConnection2 = new Connection();
         myConnection2.Name = "Second connection.";
         Display myDisplay = new Display();
         myConnection1.MessageArrived += myDisplay.DisplayMessage;
         myConnection2.MessageArrived += myDisplay.DisplayMessage;
         myConnection1.Connect();
         myConnection2.Connect();
         System.Threading.Thread.Sleep(200);
         Console.ReadKey();
      }

发送一个引发事件的对象引用,把这个对象作为事件处理程序的一个参数,就可以为不同的对象定制处理程序的响应。可以利用这个对象访问源对象,包括它的属性。
通过发送包含在派生于System.EventArgs类中的参数,就可以将其他信息作为参数提供。这些参数也得益于多态性。为MessageArrived事件定义一个处理程序:

 public void DisplayMessage(object source, MessageArrivedEventArgs e)
      {
         Console.WriteLine("Message arrived from: {0}",
                           ((Connection)source).Name);
         Console.WriteLine("Message Text: {0}", e.Message);
      }

这个处理程序可以处理不限于Timers.Elapsed的事件。但是要修改里面的代码,并注意检查null值。

EventHandler和泛型EventHandler<T>类型

这里有一个规范,事件处理程序的返回类型应为void,参数应该是两个,第一个是object,第二个参数是派生于System.EventArgs。为此.Net 提供了两个委托类型EventHandlerEventHandle <T>,以便定义事件。上面的例子中,删去了自己定义的委托转而使用EventHandle<T>泛型委托。

public event EventHandler<MessageArrivedEventArgs> MessageArrived;

上面提到的两个委托类型EventHandlerEventHandle <T>,他们的返回类型都是void,参数列表

public delegate void EventHandler(object sender, System.EventArgs e)
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)

这显然简化了我们的代码。一般,在定义事件时,最好使用这些委托类型。如果事件不需要事件实参数据,仍然可以用EventHandler委托类型,但是要传递EventArgs.Empty作为实参。
也可以为事件提供返回类型,但这有一个问题。引发给定的事件,可能会调用多个事件处理程序。如果这些处理程序都返回一个值,那么我们只能得到最后一个订阅该事件的处理程序的返回值。有些情况下可能是有用的,但最好使用void作为返回类型。

匿名方法

前面提到过匿名方法,是为用作委托目的而创建的。要创建匿名方法,使用以下代码:

delegate(parameters){
    //具体代码
}

其中parameters是一个参数列表,这些参数列表匹配正在实例化的委托类型,由匿名方法使用,例如:

delegate(Connection source, MessageArrivedEventArgs e){
> Console.WriteLine("Message arrived from: {0}",
                           ((Connection)source).Name);
         Console.WriteLine("Message Text: {0}", e.Message);
> };

使用下面的代码可以完全绕过Display.DisplayMessage()方法:

myConnection1.MessageArrived += delegate(Connection source, MessageArrivedEventArgs e){
Console.WriteLine("Message arrived from: {0}",
                           ((Connection)source).Name);
         Console.WriteLine("Message Text: {0}", e.Message);
         }


示例代码和部分内容来自《C#入门经典(第六版)》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • 事件 基本用法 关键字event,声明格式为: public event <委托类型> <事件对象> 事件的处理方...
    CieloSun阅读 2,011评论 0 0
  • 一、理解事件事件采用发布/订阅模型,其中发行者决定在什么情况下引发事件,而订户决定为响应事件而执行的操作。事件可以...
    CarlDonitz阅读 297评论 0 0
  • Asio分为独立版和Boost版。两者使用方法基本一致,只是头文件不同。Boost版是作为Boost的子库提供的。...
    果冻虾仁阅读 4,301评论 0 0
  • 《你所担心的事情九成都不会发生》这是一本书名,也是生活中的一些事实。你们有没有经常瞎操心了,瞎操心只会让我们越...
    诚思心梦阅读 162评论 0 3