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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/803242/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 01:50:35  来源:igfitidea点击:

Understanding events and event handlers in C#

c#.neteventsevent-handling

提问by Levi Campbell

I understand the purpose of events, especially within the context of creating user interfaces. I think this is the prototype for creating an event:

我了解事件的目的,尤其是在创建用户界面的上下文中。我认为这是创建事件的原型:

public void EventName(object sender, EventArgs e);

What do event handlers do, why are they needed, and how do I to create one?

事件处理程序的作用是什么,为什么需要它们,以及如何创建一个?

采纳答案by Rex M

To understand event handlers, you need to understand delegates. In C#, you can think of a delegate as a pointer (or a reference) to a method. This is useful because the pointer can be passed around as a value.

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

The central concept of a delegate is its signature, or shape. That is (1) the return type and (2) the input arguments. For example, if we create a delegate void MyDelegate(object sender, EventArgs e), it can only point to methods which return void, and take an objectand EventArgs. Kind of like a square hole and a square peg. So we say these methods have the same signature, or shape, as the delegate.

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

So knowing how to create a reference to a method, let's think about the purpose of events: we want to cause some code to be executed when something happens elsewhere in the system - or "handle the event". To do this, we create specific methods for the code we want to be executed. The glue between the event and the methods to be executed are the delegates. The event must internally store a "list" of pointers to the methods to call when the event is raised.* Of course, to be able to call a method, we need to know what arguments to pass to it! We use the delegate as the "contract" between the event and all the specific methods that will be called.

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

So the default EventHandler(and many like it) represents a specific shape of method(again, void/object-EventArgs). When you declare an event, you are saying which shape of method(EventHandler) that event will invoke, by specifying a delegate:

因此,默认值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");

(*This is the key to events in .NET and peels away the "magic" - an event is really, under the covers, just a list of methods of the same "shape". The list is stored where the event lives. When the event is "raised", it's really just "go through this list of methods and call each one, using these values as the parameters". Assigning an event handler is just a prettier, easier way of adding your method to this list of methods to be called).

(*这是 .NET 中事件的关键,它揭开了“魔法”的面纱——在幕后,事件实际上只是具有相同“形状”的方法列表。该列表存储在事件所在的位置。当事件被“引发”,它实际上只是“遍历此方法列表并调用每个方法,使用这些值作为参数”。分配事件处理程序只是将方法添加到此方法列表中的一种更漂亮、更简单的方法被称为)。

回答by Andy

That is actually the declaration for an event handler - a method that will get called when an event is fired. To create an event, you'd write something like this:

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

public class Foo
{
    public event EventHandler MyEvent;
}

And then you can subscribe to the event like this:

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

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

With OnMyEvent() defined like this:

OnMyEvent() 定义如下:

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

Whenever Foofires off MyEvent, then your OnMyEventhandler will be called.

每当Foofire off 时MyEventOnMyEvent就会调用您的处理程序。

You don't always have to use an instance of EventArgsas the second parameter. If you want to include additional information, you can use a class derived from EventArgs(EventArgsis the base by convention). For example, if you look at some of the events defined on Controlin WinForms, or FrameworkElementin WPF, you can see examples of events that pass additional information to the event handlers.

您不必总是使用 的实例EventArgs作为第二个参数。如果要包含其他信息,可以使用派生自EventArgs(EventArgs按照约定是基类) 的类。例如,如果您查看Control在 WinForms 或FrameworkElementWPF 中定义的一些事件,您可以看到将附加信息传递给事件处理程序的事件示例。

回答by tofi9

C# knows two terms, delegateand event. Let's start with the first one.

C# 知道两个术语,delegateevent. 让我们从第一个开始。

Delegate

代表

A delegateis a reference to a method. Just like you can create a reference to an instance:

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

MyClass instance = myFactory.GetInstance();

You can use a delegate to create an reference to a method:

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

Action myMethod = myFactory.GetInstance;

Now that you have this reference to a method, you can call the method via the reference:

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

MyClass instance = myMethod();

But why would you? You can also just call myFactory.GetInstance()directly. In this case you can. However, there are many cases to think about where you don't want the rest of the application to have knowledge of myFactoryor to call myFactory.GetInstance()directly.

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

An obvious one is if you want to be able to replace myFactory.GetInstance()into myOfflineFakeFactory.GetInstance()from one central place (aka factory method pattern).

一个明显的问题是,如果您希望能够从一个中心位置替换myFactory.GetInstance()myOfflineFakeFactory.GetInstance()(又名工厂方法模式)。

Factory method pattern

工厂方法模式

So, if you have a TheOtherClassclass and it needs to use the myFactory.GetInstance(), this is how the code will look like without delegates (you'll need to let TheOtherClassknow about the type of your myFactory):

所以,如果你有一个TheOtherClass类并且它需要使用myFactory.GetInstance(),这就是没有委托的代码的样子(你需要TheOtherClass知道你的类型myFactory):

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


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

}

If you'd use delegates, you don't have to expose the type of my factory:

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

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


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

}

Thus, you can give a delegate to some other class to use, without exposing your type to them. The only thing you're exposing is the signature of your method (how many parameters you have and such).

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

"Signature of my method", where did I hear that before? O yes, interfaces!!! interfaces describe the signature of a whole class. Think of delegates as describing the signature of only one method!

“我的方法签名”,我以前在哪里听说过?哦,是的,接口!!!接口描述了整个类的签名。将委托视为仅描述一种方法的签名!

Another large difference between an interface and a delegate is that when you're writing your class, you don't have to say to C# "this method implements that type of delegate". With interfaces, you do need to say "this class implements that type of an interface".

接口和委托之间的另一个很大区别是,当您编写类时,您不必对 C# 说“此方法实现了那种类型的委托”。对于接口,您确实需要说“这个类实现了那种类型的接口”。

Further, a delegate reference can (with some restrictions, see below) reference multiple methods (called MulticastDelegate). This means that when you call the delegate, multiple explicitly-attached methods will be executed. An object reference can always only reference to one object.

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

The restrictions for a MulticastDelegateare that the (method/delegate) signature should not have any return value (void) and the keywords outand refis not used in the signature. Obviously, you can't call two methods that return a number and expect them to return the same number. Once the signature complies, the delegate is automatically a MulticastDelegate.

a 的限制MulticastDelegate是(方法/委托)签名不应有任何返回值 ( void) 和关键字outref并且不在签名中使用。显然,您不能调用两个返回数字的方法并期望它们返回相同的数字。一旦签名符合要求,委托将自动成为MulticastDelegate.

Event

事件

Events are just properties (like the get;set; properties to instance fields) which expose subscription to the delegate from other objects. These properties, however, don't support get;set;. Instead, they support add; remove;

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

So you can have:

所以你可以有:

    Action myField;

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

Usage in UI (WinForms,WPF,UWP So on)

UI 中的使用(WinForms、WPF、UWP 等)

So, now we know that a delegate is a reference to a method and that we can have an event to let the world know that they can give us their methods to be referenced from our delegate, and we are a UI button, then: we can ask anyone who is interested in whether I was clicked, to register their method with us (via the event we exposed). We can use all those methods that were given to us and reference them by our delegate. And then, we'll wait and wait.... until a user comes and clicks on that button, then we'll have enough reason to invoke the delegate. And because the delegate references all those methods given to us, all those methods will be invoked. We don't know what those methods do, nor we know which class implements those methods. All we do care about is that someone was interested in us being clicked, and gave us a reference to a method that complied with our desired signature.

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

Java

爪哇

Languages like Java don't have delegates. They use interfaces instead. The way they do that is to ask anyone who is interested in 'us being clicked', to implement a certain interface (with a certain method we can call), then give us the whole instance that implements the interface. We keep a list of all objects implementing this interface and can call their 'certain method we can call' whenever we get clicked.

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

回答by Gary Willoughby

Here is a code example which may help:

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

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();
    }
  }
}

回答by Mathieu Guindon

Just to add to the existing great answers here - building on the code in the accepted one, which uses a delegate void MyEventHandler(string foo)...

只是为了添加到这里现有的好答案 - 在已接受的代码的基础上构建,它使用delegate void MyEventHandler(string foo)......

Because the compiler knows the delegate type of the SomethingHappenedevent, this:

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

myObj.SomethingHappened += HandleSomethingHappened;

Is totally equivalent to:

完全等同于:

myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

And handlers can also be unregisteredwith -=like this:

而且处理器也可以是未注册使用-=这样的:

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

For completeness' sake, raising the event can be done like this, only in the class that owns the event:

为了完整起见,可以像这样引发事件,仅在拥有该事件的类中:

//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!");
}

The thread-local copy of the handler is needed to make sure the invocation is thread-safe - otherwise a thread could go and unregister the last handler for the event immediately after we checked if it was null, and we would have a "fun" NullReferenceExceptionthere.

需要处理程序的线程本地副本以确保调用是线程安全的 - 否则线程可以在我们检查事件的最后一个处理程序是否为 之后立即取消注册该事件的最后一个处理程序null,我们将在NullReferenceException那里“玩得开心” .



C# 6 introduced a nice short hand for this pattern. It uses the null propagation operator.

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

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

回答by KE50

My understanding of the events is;

我对事件的理解是;

Delegate:

代表:

A variable to hold reference to method / methods to be executed. This makes it possible to pass around methods like a variable.

保存对要执行的方法/方法的引用的变量。这使得可以像传递变量一样传递方法。

Steps for creating and calling the event:

创建和调用事件的步骤:

  1. The event is an instance of a delegate

  2. Since an event is an instance of a delegate, then we have to first define the delegate.

  3. Assign the method / methods to be executed when the event is fired (Calling the delegate)

  4. Fire the event (Call the delegate)

  1. 该事件是一个委托的实例

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

  3. 分配触发事件时要执行的方法(调用委托

  4. 触发事件(调用委托

Example:

例子:

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();
            }
        }

    }   

}

回答by user3902302

I agree with KE50 except that I view the 'event' keyword as an alias for 'ActionCollection' since the event holds a collection of actions to be performed (ie. the delegate).

我同意 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();
        }
    }

}   

}

回答by Bilgi Sayar

//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);

回答by rileyss

publisher: where the events happen. Publisher should specify which delegate the class is using and generate necessary arguments, pass those arguments and itself to the delegate.

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

subscriber: where the response happen. Subscriber should specify methods to respond to events. These methods should take the same type of arguments as the delegate. Subscriber then add this method to publisher's delegate.

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

Therefore, when the event happen in publisher, delegate will receive some event arguments (data, etc), but publisher has no idea what will happen with all these data. Subscribers can create methods in their own class to respond to events in publisher's class, so that subscribers can respond to publisher's events.

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

回答by Siraf

Great technical answers in the post! I have nothing technicallyto add to that.

帖子中有很棒的技术答案!我在技术上没有什么要补充的。

One of the main reasons why new features appear in languages and software in general is marketing or company politics! :-) This must not be under estimated!

新功能出现在语言和软件中的主要原因之一是营销或公司政治!:-) 这个估计肯定不行!

I think this applies to certain extend to delegates and events too! i find them useful and add value to the C# language, but on the other hand the Java language decided not to use them! they decided that whatever you are solving with delegates you can already solve with existing features of the language i.e. interfaces e.g.

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

Now around 2001 Microsoft released the .NET framework and the C# language as a competitor solution to Java, so it was good to have NEW FEATURES that Java doesn't have.

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