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

25 浏览
0 Comments

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

我理解事件的目的,特别是在创建用户界面的上下文中。我认为以下是创建事件的原型:

public void EventName(object sender, EventArgs e);

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

0
0 Comments

在C#中,事件和事件处理程序的理解是一个常见的问题。事件处理程序是在事件触发时调用的方法。要创建一个事件,可以像这样编写代码:

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中定义的一些事件或WPF中的`FrameworkElement`中就可以看到将附加信息传递给事件处理程序的示例。

感谢回答问题而不涉及委托和事件。

我建议不要使用`OnXXX`命名模式作为事件处理程序的命名方式。在MFC中,`OnXXX`被解释为“处理XXX”,而在.NET中被解释为“触发XXX”,因此现在它的含义不清楚和混乱。更好的命名方式是`RaiseXXX`来触发事件,`HandleXXX`或`Sender_XXX`作为事件处理程序的命名方式。

你能展示一个简单的WinForms应用程序的工作示例吗?

0
0 Comments

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

C#中有两个术语,委托(delegate)和事件(event)。让我们从第一个开始。

委托(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#“这个方法实现了那种类型的委托”。而对于接口,你确实需要告诉“这个类实现了那种接口的类型”。

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

多路广播委托的限制是(方法/委托)签名不应该有任何返回值(`void`),并且在签名中不使用`out`和`ref`关键字。显然,你不能调用两个返回一个数字的方法并期望它们返回相同的数字。一旦签名符合,委托自动成为多路广播委托。

事件

事件只是属性(类似于对实例字段的get;set;属性),它们公开了对来自其他对象的委托的订阅。然而,这些属性不支持get;set;。相反,它们支持add; remove;

所以你可以有:

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

在UI中的使用(WinForms,WPF,UWP等)

所以,现在我们知道委托是对方法的引用,我们可以通过事件告诉世界他们可以给我们他们的方法来引用我们的委托,我们是一个UI按钮,然后:我们可以要求任何对我们的点击感兴趣的人通过我们公开的事件来注册他们的方法。我们可以使用给我们的所有方法,并通过我们的委托引用它们。然后,我们将等待...直到用户点击该按钮,然后我们将有足够的理由来调用委托。因为委托引用了所有给我们的方法,所有这些方法都将被调用。我们不知道这些方法做什么,也不知道哪个类实现了这些方法。我们关心的是有人对我们的点击感兴趣,并给我们一个符合我们期望的方法的引用。

Java

像Java这样的语言没有委托。它们使用接口来替代。它们的做法是要求任何对“我们被点击”感兴趣的人实现一个特定的接口(包含我们可以调用的特定方法),然后给我们实现该接口的整个实例。我们保留所有实现此接口的对象的列表,并且每当我们被点击时,可以调用它们的“我们可以调用的特定方法”。

感谢解释,但事件和委托实例之间有什么不同?它们看起来完全一样?

这是因为它们是“接受订阅者的委托” - 事件只是语法糖,仅此而已。

多路广播委托的限制是“多路广播委托的(方法/委托)签名不应该有任何返回值(`void`)”,我认为这是不正确的。如果它们具有返回值,它将返回最后一个值。

“因此,你可以将委托给其他类使用,而不向它们公开你的类型。你只暴露了你的方法的签名...” - 这对我来说是关键点。谢谢!

0
0 Comments

理解C#中的事件和事件处理程序的原因和解决方法

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

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

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

因此,默认的EventHandler(以及许多类似的委托)表示特定形状的方法(再次是void/object-EventArgs)。当声明一个事件时,通过指定一个委托来指定该事件将调用的哪种形状的方法(EventHandler):

//此委托可用于指向返回void并带有一个字符串的方法。
public delegate void MyEventHandler(string foo);
//此事件可以导致任何符合MyEventHandler的方法被调用。
public event MyEventHandler SomethingHappened;
//当SomethingHappened触发时,这是我希望执行的一些代码。
void HandleSomethingHappened(string foo)
{
    //做一些事情
}
//我正在创建一个委托(指针)指向HandleSomethingHappened
//并将其添加到SomethingHappened的“事件处理程序”列表中。
myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);
//在方法中触发事件。
SomethingHappened("bar");

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

现在,有人能解释为什么事件被称为EventHandler吗?在所有令人困惑的命名约定中,这是最糟糕的...

在Go中,事件不被称为EventHandler-EventHandler是事件必须与之通信的任何人的合同。就像“string MyString”一样-字符串声明了类型。事件MyEventHandler TheEvent声明了与此事件交互的任何人必须符合MyEventHandler合同。处理程序的约定是因为合同主要描述了如何处理事件。

M:谢谢你给出的对“MyEventHandler”的第一个连贯解释 🙂

感谢你给出的这句话:“事件和要执行的方法之间的粘合剂是委托。”这真的太棒了。

值得注意的是为什么需要event关键字。它限制了对不拥有事件的类型的访问,只允许+=和-=操作。

0