C#事件处理(与Java相比)

我目前正在使用delagates在C#中很难理解和实现事件。 我习惯了Java的做事方式:

  1. 为侦听器类型定义一个接口,该接口包含许多方法定义
  2. 如果我对侦听器中定义的所有事件不感兴趣,请为该接口定义适配器类以使事情更容易
  3. 在类中定义Add,Remove和Get []方法以引发事件
  4. 定义受保护的fire方法,以便在添加的侦听器列表中循环并调用正确的方法

这个我理解(并且喜欢!) – 我知道我可以在c#中完全相同,但似乎有一个新的(更好的?)系统用于c#。 阅读了无数的教程,解释了c#中代表和事件的使用后,我仍然无法真正了解正在发生的事情:S


简而言之,对于以下方法,我将如何在c#中实现事件系统:

void computerStarted(Computer computer); void computerStopped(Computer computer); void computerReset(Computer computer); void computerError(Computer computer, Exception error); 

^上面的方法取自我曾经制作的Java应用程序,我正在尝试移植到c#。

非常感谢!

您将创建四个事件,以及引发它们的方法,以及一个新的基于EventArgs的类来指示错误:

 public class ExceptionEventArgs : EventArgs { private readonly Exception error; public ExceptionEventArgs(Exception error) { this.error = error; } public Error { get { return error; } } } public class Computer { public event EventHandler Started = delegate{}; public event EventHandler Stopped = delegate{}; public event EventHandler Reset = delegate{}; public event EventHandler Error = delegate{}; protected void OnStarted() { Started(this, EventArgs.Empty); } protected void OnStopped() { Stopped(this, EventArgs.Empty); } protected void OnReset() { Reset(this, EventArgs.Empty); } protected void OnError(Exception e) { Error(this, new ExceptionEventArgs(e)); } } 

然后,类将使用方法或匿名函数订阅事件:

 someComputer.Started += StartEventHandler; // A method someComputer.Stopped += delegate(object o, EventArgs e) { Console.WriteLine("{0} has started", o); }; someComputer.Reset += (o, e) => Console.WriteLine("{0} has been reset"); 

有关上述内容的一些注意事项:

  • OnXXX方法受到保护,因此派生类可以引发事件。 这并不总是必要的 – 按照您认为合适的方式进行。
  • 每个事件声明的delegate{}片段只是避免必须进行空检查的技巧。 它为每个事件订阅了一个no-op事件处理程序
  • 事件声明是类似字段的事件 。 实际创建的是变量事件。 在课堂上你看到变量; 在课外,你会看到这个事件。

有关事件的更多详细信息,请参阅我的活动/代表文章。

你必须为此定义一个委托

 public delegate void ComputerEvent(object sender, ComputerEventArgs e); 

ComputerEventArgs将定义如下:

 public class ComputerEventArgs : EventArgs { // TODO wrap in properties public Computer computer; public Exception error; public ComputerEventArgs(Computer aComputer, Exception anError) { computer = aComputer; error = anError; } public ComputerEventArgs(Computer aComputer) : this(aComputer, null) { } } 

触发事件的类将包含以下内容:

 public YourClass { ... public event ComputerEvent ComputerStarted; public event ComputerEvent ComputerStopped; public event ComputerEvent ComputerReset; public event ComputerEvent ComputerError; ... } 

这是您为事件分配处理程序的方法:

 YourClass obj = new YourClass(); obj.ComputerStarted += new ComputerEvent(your_computer_started_handler); 

你的处理程序是:

 private void ComputerStartedEventHandler(object sender, ComputerEventArgs e) { // do your thing. } 

主要区别在于,在C#中,事件不是基于接口的。 相反,事件发布者声明您可以将其视为函数指针的委托(尽管不完全相同:-))。 然后,订阅者将事件原型实现为常规方法,并将该委托的新实例添加到发布者的事件处理程序链中。 阅读有关代表和活动的更多信息。

您还可以在此处阅读C#与Java事件的简短比较。

首先,.Net中有一个标准方法签名,通常用于事件。 语言允许任何类型的方法签名用于事件,并且有一些专家认为该约定存在缺陷(我大多同意),但它就是这样,我会在这个例子中遵循它。

  1. 创建一个包含事件参数的类(派生自EventArgs)。
 public class ComputerEventArgs:EventArgs 
 {
  电脑电脑; 
   //构造函数,属性等
 }
  1. 在要触发事件的类上创建公共事件。
     class ComputerEventGenerator //我选择了一个可怕的名字BTW。
     {
      公共事件EventHandler  ComputerStarted;
      公共事件EventHandler  ComputerStopped;
      公共事件EventHandler  ComputerReset;
     ...
     }
  1. 调用事件。
     class ComputerEventGenerator
     {
     ...
       private void OnComputerStarted(计算机计算机) 
       {
         EventHandler  temp = ComputerStarted;
         if(temp!= null)temp(这个,新的ComputerEventArgs(计算机));  //如果事件是静态的,则将“this”替换为null
       }
      }
  1. 附加事件的处理程序。
     void OnLoad()
     {
       ComputerEventGenerator computerEventGenerator = new ComputerEventGenerator();
       computerEventGenerator.ComputerStarted + = new EventHandler (ComputerEventGenerator_ComputerStarted);
     }
  1. 创建刚刚附加的处理程序(主要是通过按VS中的Tab键)。
     private void ComputerEventGenerator_ComputerStarted(object sender,ComputerEventArgs args)
     {
       if(args.Computer.Name ==“HAL9000”)
          ShutItDownNow(args.Computer);
     }
  1. 完成后不要忘记分离处理程序。 (忘记这样做是C#内存泄漏的最大来源!)
     void OnClose()
     {
       ComputerEventGenerator.ComputerStarted  -  = ComputerEventGenerator_ComputerStarted;
     }

就是这样!

编辑:我老实说无法弄清楚为什么我的编号点都显示为“1”。 我讨厌电脑。

有几种方法可以做你想要的。 最直接的方法是为托管类中的每个事件定义委托,例如

 public delegate void ComputerStartedDelegate(Computer computer); protected event ComputerStartedDelegate ComputerStarted; public void OnComputerStarted(Computer computer) { if (ComputerStarted != null) { ComputerStarted.Invoke(computer); } } protected void someMethod() { //... computer.Started = true; //or whatever OnComputerStarted(computer); //... } 

任何对象都可以通过以下方式“监听”此事件:

 Computer comp = new Computer(); comp.ComputerStarted += new ComputerStartedDelegate( this.ComputerStartedHandler); protected void ComputerStartedHandler(Computer computer) { //do something } 

执行此操作的“推荐标准方法”是定义EventArgs的子类以保存计算机(以及旧/新状态和exception)值,将4个委托减少为1。 在这种情况下,这将是一个更清洁的解决方案,尤其是 在以后扩展的情况下使用枚举用于计算机状态。 但基本技术仍然是相同的:

  • 委托定义事件处理程序/侦听器的签名/接口
  • 事件数据成员是“听众”列表

使用 – =语法而不是+ =删除侦听器

在c#中,事件是代表。 它们的行为方式与C / C ++中的函数指针类似,但它们是从System.Delegate派生的实际类。

在这种情况下,创建一个自定义EventArgs类来传递Computer对象。

 public class ComputerEventArgs : EventArgs { private Computer _computer; public ComputerEventArgs(Computer computer) { _computer = computer; } public Computer Computer { get { return _computer; } } } 

然后公开生产者的事件:

 public class ComputerEventProducer { public event EventHandler Started; public event EventHandler Stopped; public event EventHandler Reset; public event EventHandler Error; /* // Invokes the Started event */ private void OnStarted(Computer computer) { if( Started != null ) { Started(this, new ComputerEventArgs(computer)); } } // Add OnStopped, OnReset and OnError } 

然后,事件的使用者将处理程序函数绑定到使用者上的每个事件。

 public class ComputerEventConsumer { public void ComputerEventConsumer(ComputerEventProducer producer) { producer.Started += new EventHandler(ComputerStarted); // Add other event handlers } private void ComputerStarted(object sender, ComputerEventArgs e) { } } 

当ComputerEventProducer调用OnStarted时,将调用Started事件,然后调用ComputerEventConsumer.ComputerStarted方法。

委托声明了一个函数签名,当它被用作类的事件时,它也充当了登记的调用目标的集合。 事件上的+ =和 – =语法用于将目标添加到列表中。

鉴于以下代理用作事件:

 // arguments for events public class ComputerEventArgs : EventArgs { public Computer Computer { get; set; } } public class ComputerErrorEventArgs : ComputerEventArgs { public Exception Error { get; set; } } // delegates for events public delegate void ComputerEventHandler(object sender, ComputerEventArgs e); public delegate void ComputerErrorEventHandler(object sender, ComputerErrorEventArgs e); // component that raises events public class Thing { public event ComputerEventHandler Started; public event ComputerEventHandler Stopped; public event ComputerEventHandler Reset; public event ComputerErrorEventHandler Error; } 

您可以使用以下内容订阅这些事件:

 class Program { static void Main(string[] args) { var thing = new Thing(); thing.Started += thing_Started; } static void thing_Started(object sender, ComputerEventArgs e) { throw new NotImplementedException(); } } 

尽管参数可以是任何参数,但是对象发送者和EventArgs e是一种非常一致的约定。 + = thing_started将首先创建指向目标方法的委托实例,然后将其添加到事件中。

在组件本身上,您通常会添加触发事件的方法:

 public class Thing { public event ComputerEventHandler Started; public void OnStarted(Computer computer) { if (Started != null) Started(this, new ComputerEventArgs {Computer = computer}); } } 

如果没有将任何代理添加到事件中,则必须测试null。 当您进行方法调用时,将调用已添加的所有代理。 这就是为什么对于事件,返回类型为void – 没有单个返回值 – 因此要反馈信息,您将在事件处理程序将更改的EventArgs上具有属性。

另一个改进是使用通用的EventHandler委托,而不是为每种类型的args声明一个具体的委托。

 public class Thing { public event EventHandler Started; public event EventHandler Stopped; public event EventHandler Reset; public event EventHandler Error; } 

非常感谢你的回答! 最后,我开始明白发生了什么。 就一件事; 看来如果每个事件都有不同数量/类型的参数,我需要创建一个不同的:: EventArgs类来处理它:

 public void computerStarted(Computer computer); public void computerStopped(Computer computer); public void computerReset(Computer computer); public void breakPointHit(Computer computer, int breakpoint); public void computerError(Computer computer, Exception exception); 

这将需要三个阶段来处理事件!? (两个自定义,一个使用默认的EventArgs.Empty类)

干杯!

好的,最后的澄清!:这是我能以代码方式实现这些事件的最佳方式吗?

  public class Computer { public event EventHandler Started; public event EventHandler Stopped; public event EventHandler Reset; public event EventHandler BreakPointHit; public event EventHandler Error; public Computer() { Started = delegate { }; Stopped = delegate { }; Reset = delegate { }; BreakPointHit = delegate { }; Error = delegate { }; } protected void OnStarted() { Started(this, EventArgs.Empty); } protected void OnStopped() { Stopped(this, EventArgs.Empty); } protected void OnReset() { Reset(this, EventArgs.Empty); } protected void OnBreakPointHit(int breakPoint) { BreakPointHit(this, new BreakPointEvent(breakPoint)); } protected void OnError(System.Exception exception) { Error(this, new ExceptionEvent(exception)); } } }