了解C#中的事件和事件处理程序

时间:2009-04-29 16:41:24

标签: c# .net events event-handling

我理解事件的目的,特别是在创建用户界面的环境中。我认为这是创建活动的原型:

public void EventName(object sender, EventArgs e);

事件处理程序做了什么,为什么需要它们,以及如何创建一个?

12 个答案:

答案 0 :(得分:594)

要了解事件处理程序,您需要了解delegates。在C#中,您可以将委托视为方法的指针(或引用)。这很有用,因为指针可以作为值传递。

代表的核心概念是其签名或形状。这是(1)返回类型和(2)输入参数。例如,如果我们创建一个委托void MyDelegate(object sender, EventArgs e),它只能指向返回void的方法,并采用objectEventArgs。有点像方孔和方形钉。所以我们说这些方法与委托具有相同的签名或形状。

因此,知道如何创建对方法的引用,让我们考虑事件的目的:我们希望在系统中其他地方发生某些事情时执行某些代码 - 或者“处理事件”。为此,我们为要执行的代码创建特定方法。事件和要执行的方法之间的粘合剂是委托。事件必须在内部存储指向引发事件时要调用的方法的“列表”。*当然,为了能够调用方法,我们需要知道要传递给它的参数!我们使用委托作为事件和将要调用的所有特定方法之间的“契约”。

因此默认的EventHandler(以及许多喜欢它)代表特定形式的方法(同样,void / object-EventArgs)。当您声明一个事件时,您通过指定一个委托来说明该事件将调用的方法形式(EventHandler):

//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyEventHandler(string foo);

//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyEventHandler SomethingHappened;

//Here is some code I want to be executed
//when SomethingHappened fires.
void HandleSomethingHappened(string foo)
{
    //Do some stuff
}

//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

//To raise the event within a method.
SomethingHappened("bar");

(*这是.NET中事件的关键,剥夺了“神奇” - 事实上,事实上只是一个相同“形状”的方法列表。列表存储在事件的位置当事件被“提升”时,它实际上只是“通过这个方法列表并调用每个方法,使用这些值作为参数”。分配事件处理程序只是一种更简洁,更简单的方法来添加您的方法要调用的方法列表。)

答案 1 :(得分:94)

C#知道两个词delegateevent。让我们从第一个开始。

代表

delegate是对方法的引用。就像您可以创建对实例的引用一样:

MyClass instance = myFactory.GetInstance();

您可以使用委托创建对方法的引用:

Action myMethod = myFactory.GetInstance;

现在您已经有了对方法的引用,您可以通过引用调用该方法:

MyClass instance = myMethod();

但你为什么要这样?您也可以直接致电myFactory.GetInstance()。在这种情况下你可以。但是,有很多情况需要考虑您不希望其他应用程序知道myFactory或直接调用myFactory.GetInstance()的情况。

显而易见的一个是,如果您希望能够从一个中心位置(又名工厂方法模式)将myFactory.GetInstance()替换为myOfflineFakeFactory.GetInstance()

工厂方法模式

因此,如果您有一个TheOtherClass类,并且需要使用myFactory.GetInstance(),那么这就是代码在没有代理的情况下的样子(您需要让TheOtherClass知道关于myFactory)的类型:

TheOtherClass toc;
//...
toc.SetFactory(myFactory);


class TheOtherClass
{
   public void SetFactory(MyFactory factory)
   {
      // set here
   }

}

如果您使用代表,则不必公开我的工厂类型:

TheOtherClass toc;
//...
Action factoryMethod = myFactory.GetInstance;
toc.SetFactoryMethod(factoryMethod);


class TheOtherClass
{
   public void SetFactoryMethod(Action factoryMethod)
   {
      // set here
   }

}

因此,您可以将委托给一些其他类使用,而不会将您的类型暴露给它们。你唯一暴露的是方法的签名(你有多少参数等等)。

“我的方法签名”,之前我在哪里听到过?是的,接口!!!接口描述了整个类的签名。把代表想象成只描述一种方法的签名!

接口和委托之间的另一个重要区别是,当您编写类时,您不必对C#说“此方法实现了该类型的委托”。使用接口,您需要说“此类实现了该类型的接口”。

此外,委托引用可以(有一些限制,见下文)引用多个方法(称为MulticastDelegate)。这意味着当您调用委托时,将执行多个显式附加的方法。对象引用始终只能引用一个对象。

MulticastDelegate的限制是(方法/委托)签名不应具有任何返回值(void)且关键字outref不是用于签名。显然,你不能调用两个返回数字的方法,并期望它们返回相同的数字。签名符合后,代表将自动为MulticastDelegate

事件

事件只是属性(如get; set;属性到实例字段),它们从其他对象公开对委托的订阅。但是,这些属性不支持get; set;。相反,他们支持添加;除去;

所以你可以:

    Action myField;

    public event Action MyProperty
    {
        add { myField += value; }
        remove { myField -= value; }
    }

用户界面(WinForms,WPF,UWP等)

所以,现在我们知道一个委托是一个方法的引用,我们可以有一个事件让世界知道他们可以给我们他们的方法从我们的委托中引用,我们是一个UI按钮,那么:我们可以问任何对我是否被点击感兴趣的人,向我们注册他们的方法(通过我们公开的事件)。我们可以使用提供给我们的所有方法,并由我们的代表参考。然后,我们将等待等待....直到用户点击该按钮,然后我们才有足够的理由调用该委托。并且因为委托引用了给我们的所有方法,所以这些方法都将被调用。我们不知道这些方法做了什么,也不知道哪个类实现了这些方法。我们所关心的只是有人对我们被点击感兴趣,并且给了我们一个符合我们所需签名的方法的参考。

爪哇

像Java这样的语言没有委托。他们使用接口代替。他们这样做的方式是询问任何对“我们被点击”感兴趣的人,实现某个界面(我们可以调用某种方法),然后给我们实现界面的整个实例。我们保留一个实现此界面的所有对象的列表,并且可以在我们点击时调用他们的“我们可以调用的特定方法”。

答案 2 :(得分:38)

这是一个可能有用的代码示例:

using System;
using System.Collections.Generic;
using System.Text;

namespace Event_Example
{
  // First we have to define a delegate that acts as a signature for the
  // function that is ultimately called when the event is triggered.
  // You will notice that the second parameter is of MyEventArgs type.
  // This object will contain information about the triggered event.

  public delegate void MyEventHandler(object source, MyEventArgs e);

  // This is a class which describes the event to the class that receives it.
  // An EventArgs class must always derive from System.EventArgs.

  public class MyEventArgs : EventArgs
  {
    private string EventInfo;

    public MyEventArgs(string Text) {
      EventInfo = Text;
    }

    public string GetInfo() {
      return EventInfo;
    }
  }

  // This next class is the one which contains an event and triggers it
  // once an action is performed. For example, lets trigger this event
  // once a variable is incremented over a particular value. Notice the
  // event uses the MyEventHandler delegate to create a signature
  // for the called function.

  public class MyClass
  {
    public event MyEventHandler OnMaximum;

    private int i;
    private int Maximum = 10;

    public int MyValue
    {
      get { return i; }
      set
      {
        if(value <= Maximum) {
          i = value;
        }
        else 
        {
          // To make sure we only trigger the event if a handler is present
          // we check the event to make sure it's not null.
          if(OnMaximum != null) {
            OnMaximum(this, new MyEventArgs("You've entered " +
              value.ToString() +
              ", but the maximum is " +
              Maximum.ToString()));
          }
        }
      }
    }
  }

  class Program
  {
    // This is the actual method that will be assigned to the event handler
    // within the above class. This is where we perform an action once the
    // event has been triggered.

    static void MaximumReached(object source, MyEventArgs e) {
      Console.WriteLine(e.GetInfo());
    }

    static void Main(string[] args) {
      // Now lets test the event contained in the above class.
      MyClass MyObject = new MyClass();
      MyObject.OnMaximum += new MyEventHandler(MaximumReached);
      for(int x = 0; x <= 15; x++) {
        MyObject.MyValue = x;
      }
      Console.ReadLine();
    }
  }
}

答案 3 :(得分:34)

这实际上是事件处理程序的声明 - 一个在触发事件时将被调用的方法。要创建一个事件,你可以这样写:

public class Foo
{
    public event EventHandler MyEvent;
}

然后你可以订阅这样的事件:

Foo foo = new Foo();
foo.MyEvent += new EventHandler(this.OnMyEvent);

使用OnMyEvent()定义如下:

private void OnMyEvent(object sender, EventArgs e)
{
    MessageBox.Show("MyEvent fired!");
}

每当Foo触发MyEvent时,您的OnMyEvent处理程序就会被调用。

您并不总是必须使用EventArgs的实例作为第二个参数。如果要包含其他信息,可以使用从EventArgs派生的类(EventArgs是按惯例的基础)。例如,如果您查看WinForms中Control上定义的某些事件或WPF中的FrameworkElement,您可以看到将其他信息传递给事件处理程序的事件示例。

答案 4 :(得分:21)

只是在这里添加现有的好答案 - 建立在已接受的代码中,使用delegate void MyEventHandler(string foo) ...

因为编译器知道 SomethingHappened 事件的委托类型,所以:

myObj.SomethingHappened += HandleSomethingHappened;

完全等同于:

myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

处理程序也可以取消注册 -=,如下所示:

// -= removes the handler from the event's list of "listeners":
myObj.SomethingHappened -= HandleSomethingHappened;

为了完整起见,举办活动可以这样做,只在拥有该活动的班级中进行:

//Firing the event is done by simply providing the arguments to the event:
var handler = SomethingHappened; // thread-local copy of the event
if (handler != null) // the event is null if there are no listeners!
{
    handler("Hi there!");
}

需要处理程序的线程局部副本以确保调用是线程安全的 - 否则线程可以在我们检查它是null之后立即取消注册该事件的最后一个处理程序,并且我们会在那里“有趣”NullReferenceException


C#6为这种模式引入了一个很好的简写。它使用空传播运算符。

SomethingHappened?.Invoke("Hi there!");

答案 5 :(得分:11)

我对这些事件的理解是;

代表:

用于保存对要执行的方法/方法的引用的变量。这使得传递变量等方法成为可能。

创建和调用活动的步骤:

  1. 该事件是委托的实例

  2. 由于事件是委托的实例,因此我们必须先定义委托。

  3. 分配事件被触发时要执行的方法/方法(调用代理

  4. 触发事件(调用代理

  5. 示例:

    using System;
    
    namespace test{
        class MyTestApp{
            //The Event Handler declaration
            public delegate void EventHandler();
    
            //The Event declaration
            public event EventHandler MyHandler;
    
            //The method to call
            public void Hello(){
                Console.WriteLine("Hello World of events!");
            }
    
            public static void Main(){
                MyTestApp TestApp = new MyTestApp();
    
                //Assign the method to be called when the event is fired
                TestApp.MyHandler = new EventHandler(TestApp.Hello);
    
                //Firing the event
                if (TestApp.MyHandler != null){
                    TestApp.MyHandler();
                }
            }
    
        }   
    
    }
    

答案 6 :(得分:3)

出版商:事件发生的地方。 Publisher应指定该类正在使用哪个委托并生成必要的参数,将这些参数及其自身传递给委托。

订户:响应发生的位置。订阅者应指定响应事件的方法。这些方法应采用与委托相同类型的参数。订阅者然后将此方法添加到发布者的委托。

因此,当事件发生在发布者时,委托会收到一些事件参数(数据等),但发布者不知道所有这些数据会发生什么。订阅者可以在自己的类中创建方法来响应发布者类中的事件,以便订阅者可以响应发布者的事件。

答案 7 :(得分:2)

//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyDelegate(string foo);

//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyDelegate MyEvent;

//Here is some code I want to be executed
//when SomethingHappened fires.
void MyEventHandler(string foo)
{
    //Do some stuff
}

//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.MyEvent += new MyDelegate (MyEventHandler);

答案 8 :(得分:0)

我同意KE50,除了我将'event'关键字视为'ActionCollection'的别名,因为该事件包含要执行的操作集合(即委托)。

using System;

namespace test{

class MyTestApp{
    //The Event Handler declaration
    public delegate void EventAction();

    //The Event Action Collection 
    //Equivalent to 
    //  public List<EventAction> EventActions=new List<EventAction>();
    //        
    public event EventAction EventActions;

    //An Action
    public void Hello(){
        Console.WriteLine("Hello World of events!");
    }
    //Another Action
    public void Goodbye(){
        Console.WriteLine("Goodbye Cruel World of events!");
    }

    public static void Main(){
        MyTestApp TestApp = new MyTestApp();

        //Add actions to the collection
        TestApp.EventActions += TestApp.Hello;
        TestApp.EventActions += TestApp.Goodbye;

        //Invoke all event actions
        if (TestApp.EventActions!= null){
            //this peculiar syntax hides the invoke 
            TestApp.EventActions();
            //using the 'ActionCollection' idea:
            // foreach(EventAction action in TestApp.EventActions)
            //     action.Invoke();
        }
    }

}   

}

答案 9 :(得分:0)

帖子中提供了很棒的技术解答!我没有技术上的补充。

新功能通常在语言和软件中出现的主要原因之一是市场营销或公司政治! :-)不可低估!

我认为这也适用于代表和事件的某些扩展!我发现它们很有用,并为C#语言增加了价值,但另一方面,Java语言决定不使用它们!他们认为,无论您用委托人解决什么问题,您都可以使用该语言的现有功能(例如接口)解决。

现在大约在2001年,Microsoft发布了.NET框架和C#语言作为Java的竞争解决方案,因此拥有Java没有的新功能非常好。

答案 10 :(得分:0)

我最近举了一个如何在c#中使用事件的示例,并将其发布在我的博客上。我试图通过一个非常简单的示例使它尽可能清晰。万一它可以帮助任何人,这里是:http://www.konsfik.com/using-events-in-csharp/

它包括描述和源代码(带有大量注释),并且主要关注事件和事件处理程序的正确使用(类似于模板)。

一些关键点是:

  • 事件就像“子类型的代表”,只是受到更多限制(以一种很好的方式)。实际上,事件的声明始终包含委托(EventHandlers是委托的一种)。

  • 事件处理程序是特定类型的委托(您可以将它们视为模板),它们迫使用户创建具有特定“签名”的事件。签名的格式为:(对象发送者,EventArgs eventarguments)。

  • 您可以创建自己的EventArgs子类,以包括事件需要传达的任何类型的信息。使用事件时不必使用EventHandlers。您可以完全跳过它们,并在您的位置使用自己的代理。

  • 使用事件和委托之间的一个主要区别是,即使只能将事件声明为公共,也只能在声明它们的类中调用事件。这是一个非常重要的区别,因为它允许您公开事件,以便将它们“连接”到外部方法,同时保护它们免受“外部滥用”。

答案 11 :(得分:0)

要了解的另一件事,在某些情况下,当您需要低耦合级别时必须使用“委托/事件”!

如果要在应用程序中的多个位置使用组件,则需要使组件具有较低的耦合度,并且必须委派特定的无关LOGIC <您组件的强>外!这样可以确保您的系统已解耦,代码更简洁。

SOLID 原则下,这是“ D ”( D 倾向性倒置原则)。

也称为“ IoC ”,控制反转

您可以使用事件,委托和DI (依赖项注入)制作“ IoC ”。

访问子类中的方法很容易。但是从子级访问父类中的方法更加困难。您必须将父代参考传递给孩子! (或在接口上使用DI)

代理/事件使我们无需参考即可从孩子与父母进行交流!

enter image description here

在上面的图中,我不使用Delegate / Event ,并且上级组件B 必须具有上级组件A的引用,以执行无关的业务逻辑A.(高耦合度)方法中的结果

使用这种方法,我必须放置所有使用组件B的组件的所有引用! :(

enter image description here

在上图中,我使用委托/事件,并且组件B不必知道A。(耦合程度低)

您可以在应用程序中的任何地方使用B组件