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

Lev*_*ell 310 .net c# events event-handling

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

public void EventName(object sender, EventArgs e);
Run Code Online (Sandbox Code Playgroud)

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

Rex*_*x M 625

要了解事件处理程序,您需要了解委托.在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");
Run Code Online (Sandbox Code Playgroud)

(*这是.NET中事件的关键,并剥离了"神奇" - 事件实际上只是一个相同"形状"的方法列表.列表存储在事件所在的位置.事件被"提升",它实际上只是"通过这个方法列表并调用每个方法,使用这些值作为参数".分配事件处理程序只是将方法添加到此方法列表中的更简单,更简单的方法被称为).

  • @Joel in Go事件未被调用EventHandler - EventHandler是事件必须与任何与之通信的人签订的合同.它就像"字符串MyString" - 字符串声明了类型.event MyEventHandler TheEvent声明与此事件交互的任何人都必须符合MyEventHandler合约.Handler约定是因为合同主要描述了如何处理事件. (34认同)
  • 现在任何人都可以解释为什么事件被称为EventHandler?在所有令人困惑的命名约定中,这是最糟糕的...... (20认同)
  • 该事件是如何解雇的? (15认同)
  • @Rex M:谢谢你对我见过的"MyEventHandler"的第一个连贯的解释:) (15认同)
  • 谢谢你的阶段:"事件和要执行的方法之间的粘合剂是代表.",这真的很棒. (9认同)
  • 重要的是要注意为什么需要`event`关键字.它将对不拥有事件的类型的访问限制为+ =和 - =**** (5认同)
  • Go中的@Joel:我感觉一样,直到我开始阅读它:public event <MyEventHandler> eventName :) (4认同)
  • 只想提一下:"HandleSomethingHappened"通常被称为"OnSomethingHappened" (4认同)
  • 我把这个答案印在纸上。 (3认同)
  • @LuftMensch来自拥有该事件的类,语法`EventName(delegateArg1,delegateArg2);`是有效的.它调用事件就像一个方法,这种方法启动链,调用分配给该事件的每个处理程序与这些参数. (2认同)

tof*_*fi9 96

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

代表

A delegate是对方法的引用.就像你可以创建一个实例的引用:

MyClass instance = myFactory.GetInstance();
Run Code Online (Sandbox Code Playgroud)

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

Action myMethod = myFactory.GetInstance;
Run Code Online (Sandbox Code Playgroud)

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

MyClass instance = myMethod();
Run Code Online (Sandbox Code Playgroud)

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

一个明显的例子是,如果你希望能够更换myFactory.GetInstance()myOfflineFakeFactory.GetInstance()从一个中央位置(也就是工厂方法模式).

工厂方法模式

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

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


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

}
Run Code Online (Sandbox Code Playgroud)

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

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


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

}
Run Code Online (Sandbox Code Playgroud)

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

"我的方法的签名",我之前在哪里听到过?是的,接口!!! 接口描述了整个类的签名.将代表视为仅描述一种方法的签名!

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

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

对a的限制MulticastDelegate是(方法/委托)签名不应具有任何返回值(void)和关键字out,ref并且不在签名中使用.显然,你不能调用两个返回数字的方法,并期望它们返回相同的数字.签名符合后,代表自动成为MulticastDelegate.

事件

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

所以你可以:

    Action myField;

    public event Action MyProperty
    {
        add { myField += value; }
        remove { myField -= value; }
    }
Run Code Online (Sandbox Code Playgroud)

UI中的用法(WinForms,WPF,UWP等)

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

Java的

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


Gar*_*hby 40

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

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();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 委托调用可以在C#6中简化为:`OnMaximum?.Invoke(this,new MyEventArgs("你输入了......")); (4认同)

And*_*ndy 36

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

public class Foo
{
    public event EventHandler MyEvent;
}
Run Code Online (Sandbox Code Playgroud)

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

Foo foo = new Foo();
foo.MyEvent += new EventHandler(this.OnMyEvent);
Run Code Online (Sandbox Code Playgroud)

使用OnMyEvent()定义如下:

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

每当Foo发射时MyEvent,OnMyEvent都会调用你的处理程序.

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

  • 感谢您回答这个问题,而不是进入代表和活动. (12认同)
  • 我建议不要为事件处理程序使用`OnXXX`命名模式.(愚蠢的是,OnXXX在MFC中意为'处理XXX',在.net中'提升XXX',所以现在它的含义不清楚且令人困惑 - [详见此帖](http://serialseb.blogspot. co.uk/2008/02/raising-event-without-checking-for-null.html)).首选名称为"RaiseXXX"以引发事件,而"HandleXXX"或"Sender_XXX"则为事件处理程序. (3认同)

Mat*_*don 22

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

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

myObj.SomethingHappened += HandleSomethingHappened;
Run Code Online (Sandbox Code Playgroud)

完全等同于:

myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);
Run Code Online (Sandbox Code Playgroud)

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

// -= removes the handler from the event's list of "listeners":
myObj.SomethingHappened -= HandleSomethingHappened;
Run Code Online (Sandbox Code Playgroud)

为了完整起见,可以像这样完成事件,只在拥有事件的类中:

//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!");
}
Run Code Online (Sandbox Code Playgroud)

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


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

SomethingHappened?.Invoke("Hi there!");
Run Code Online (Sandbox Code Playgroud)


KE5*_*E50 13

我对这些事件的理解是;

代表:

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

创建和调用事件的步骤:

  1. 该事件是委托的实例

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

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

  4. 解雇事件(致电代表)

例:

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

    }   

}
Run Code Online (Sandbox Code Playgroud)