繁体   English   中英

为什么事件处理程序的返回类型总是 void?

[英]Why do event handlers always have a return type of void?

嘿,我想知道为什么像这样的事件的返回类型

private void button1_Click(object sender, EventArgs e)

总是无效?

它也可以返回任何其他值吗?

事件处理程序签名,即返回类型以及它采用的参数的数量和类型,由用于定义事件的delegate的签名确定。 因此,您示例中 Button 的 Click 事件不支持任何返回值。

通常,您不会期望从事件处理程序返回一个值作为函数返回值,因为一个事件可以有多个订阅者,每个订阅者都将独立于其他处理程序返回一个返回值,并且需要特殊的事件触发代码来决定要做什么带有所有返回值。

通常,如果您需要从事件处理程序返回通信, EventArgs结构将包含处理程序可以更新的成员,并且每个处理程序将有机会查看值并相应地更新,并且触发事件的代码只需要对结构中的最终值。

一个事件可以有一个返回值。 但返回 void (并具有 2 个参数)是 BCL 指南。

当您使用事件的多播属性时,返回值变得有点混乱。 返回值是最后执行的处理程序的值。 所有其他订阅的处理程序的返回都丢失了,并且由于事件用于解耦,您对它们的执行顺序没有太多控制。 这使得返回值非常不切实际。

通过 EventArgs 后代的可写属性(例如 Window.Closing 事件的Cancel属性)共享和返回信息是 BCL 的另一种做法。 所有处理程序都可以看到并更改它。 仍然是最后一个胜利的解决方案,但更好。

但说了这么多,你仍然可以写:

delegate int Summer(int[] arr);  // delegate

class Program
{
    public event Summer OnSum;   // event

    void DoSum()
    {
        int[] data = {1, 2, 3} ;              
        int sum = 0;

        if (OnSum != null)  
          sum = OnSum(data);   // execute it.
    }
}

除了 .NET 期望标准控件中事件的特定签名这一事实之外,请考虑这一点:一个事件可以附加多个事件处理程序,将使用哪个返回值?

事件处理程序返回一个值是没有意义的。 通常,他们会修改从EventArgs派生的某个对象的状态,以将某些内容返回给触发事件的任何对象。

在 c# 中,事件可以有两种类型

1. 组播
2. 单播

多播事件是那些拥有多个订阅者的事件。 当引发多播事件时,将调用多个处理程序,因为您订阅了多个事件处理程序。 那么,多播事件旨在用于调用多个处理程序,而多播处理程序不能有返回类型,为什么??

因为如果多播委托有返回类型,那么每个事件处理程序都会返回一些值,并且一个事件处理程序的返回值将被下一个事件处理程序值替换。

假设您有一个多播委托如下

public delegate int buttonClick;

public event buttonClick onClick;

onClick += method1
onclick += method2
onclick += metho3

当引发此事件时,method1 返回的值将替换为 method2 返回的值,最终仅接收 method3 的值。

因此,在多播委托的情况下,始终建议不要返回任何值。

但在单播 deleagte 的情况下,您将只有一个订阅者。 所以你可以为实现你的目的而回报价值

因此,对于多播委托 - 无返回类型和单播委托 - 可以有返回类型

多播委托也可以返回多个值,但必须手动引发该事件。

如果您选择多播委托也应该返回值的事件,假设我有一个绑定到 4 个事件处理程序的事件,它需要两个整数,一个处理程序执行加法、第二次减法和第三次乘法和最后一次除法。 因此,如果您想获取所有处理程序的返回类型,则必须通过以下方式手动引发事件,

var handler = _eventToRaised.GetInvocationList();
foreach(var handler in handlers)
{
  if(handler != null)
   {
    var returnValue = handler()// pass the values which delegate expects.
   }

}

你不能这样做,因为处理事件的委托需要一个特定的签名(如果你尝试改变它,你会得到编译错误)。 例如,在这种情况下的委托( Button.Click )是一个System.EventHandler ,它必须匹配该签名才能作为委托编译/运行:

public delegate void EventHandler(Object sender, EventArgs e)

这就是委托的工作方式,当您查看它通常的使用方式时,它更有意义:

MyButton.Click += button1_Click;

如果你还了别的东西……它有什么用? 如果您打算调用返回结果的东西...这就是方法的用途,而不是 EventHandler :)

当然,事件可以返回值。

   [TestClass]
   public class UnitTest1 {
      delegate int EventWithReturnValue();

      class A {
         public event EventWithReturnValue SomeEvent;
         public int LastEventResult { get; set; }

         public void RaiseEvent() {
            LastEventResult = SomeEvent();
         }
      }

      [TestMethod]
      public void TestMethod1() {
         A a = new A();
         a.SomeEvent += new EventWithReturnValue(a_SomeEvent);
         a.RaiseEvent();
         Assert.AreEqual(123, a.LastEventResult);
      }

      int a_SomeEvent() {
         return 123;
      }
   }

但是,使用事件返回值在组件与其使用者之间交换信息并不常见。

默认的 EventHandler 委托定义了这个签名。 但是,如果您愿意,您可以使用自己的返回类型自由创建自己的事件。

public class AnEvent
{
  public delegate MyReturnType MyDelegateName();
  public event MyDelegateName MyEvent;

  public void DoStuff()
  {
    MyReturnType result = null;
    if (MyEvent != null)
      result = MyEvent();
    Console.WriteLine("the event was fired");
    if (result != null)
      Console.Writeline("the result is" + result.ToString());
  }
}

public class EventListener
{
  public EventListener()
  {
    var anEvent = new AnEvent();
    anEvent.MyEvent += SomeMethod;
  }

  public MyReturnType SomeMethod()
  {
    Console.Writeline("the event was handled!");
    return new MyReturnType;
  }
}

正如许多人已经指出的那样,这是一个约定而不是约束。 您可以让事件返回 EventArgs 本身中的内容。 Microsoft 在很多地方都使用了这种模式,请参阅 WinForms 中的 FormClosing 事件。 因此,如果您想返回一个值,请执行以下操作:

public class AllowCloseEventArgs : EventArgs
{
    public bool AllowClose = true;
}

public void AllowClose(object sender, AllowCloseEventArgs e)
{ e.AllowClose = false; }

知道了这一点,现在让我们讨论为什么设计师选择事件的“标准”无效返回原型:

  1. 如果它们有返回值,它会是什么类型?
  2. 如果事件返​​回一个值,该值意味着什么?
  3. 由于没有返回类型,事件可以是单向的。 这意味着如果我不关心异常,我可以“触发并忘记”事件,例如调用 Control.BeginInvoke(...)

更新:Ben 理所当然地补充说:#4:如果一个事件需要返回多个值怎么办?

返回类型是 void,因为它是一个子例程,而不是一个函数。 可以让它返回一个值,但事件处理程序(这是挂钩到按钮单击事件的子例程的内容)并非完全适用。

在 VB 中,这行代码是:

Private Sub button_Click(ByVal sender As Object, ByVal e As EventArgs)

在这种情况下,VB 中显式的“Sub”语句更有意义,但请记住,C# 中的所有void都只是子例程……它们在代码中根据参数执行某些操作,但不返回值。 但是,它们可以更改传入参数的值。

正如我的一位大学教授曾经对几乎每个问题所说的“这取决于实施”。 在这种特殊情况下,使用事件处理程序,此模型没有任何隐含的内容会阻止和实现此模式以将某些内容返回给调用代码。 但是,您传递了发送者对象和原始事件参数,基本上形成了您的执行环境上下文,无需返回任何内容,因为您可以直接使用这些引用来实现任何相关功能。

其他框架可能允许事件处理程序返回某些内容。

介绍性说明:如果您要调用的 [single] 方法与您的调用代码处于相同或更低的依赖级别,那么您可以继续调用它,不需要事件。 所以; 事件仅在您的至少一个事件引发(调用)将调用上层的方法时才有用。

现在,长答案取决于您是编写自己的委托并将其标记为事件 (1) 还是使用 EventHandler (2):

  1. 如果编写自己的委托,您可以只返回所需的结果。 但问题是,对于多播委托,只返回最终订阅方法的结果。


    public class TestEvents
    {
        public event SomethingHappenedHandler1 SomethingHappened1;
        public event EventHandler SomethingHappened2;
    
        public void Run()
        {
            SomethingHappened1 += Scenario_DelegateOnly.SubscriberMethod;
            SomethingHappened1(this, 13, 15);
    
            SomethingHappened2 += Scenario_EventHandlerAndEventArgs.SubscriberMethod;
            SomethingHappened2(this, new MyEventArgs2(33, 35));
        }
    }
    
    //1
    public delegate int SomethingHappenedHandler1(object sender, int arg1, int arg2);
    public static class Scenario_DelegateCanHaveReturnValue
    {
        public static int SubscriberMethod(object sender, int arg1, int arg2)
        {
            Console.WriteLine($"{sender.ToString()} {arg1} {arg2} ... returning {arg1} * {arg2}");
            return arg1 * arg2;
        }
    }
    
    //2
    public static class Scenario_EventHandlerAndEventArgs
    {
        public static void SubscriberMethod(object sender, MyEventArgs2 args)
        {
            Console.WriteLine($"{sender.ToString()} {args.arg1} {args.arg2}");
        }
    }
    public class MyEventArgs2 : EventArgs
    {
        public MyEventArgs2(int arg1, int arg2)
        {
            this.arg1 = arg1;
            this.arg2 = arg2;
        }
        public int arg1;
        public int arg2;
    }

  1. 如果使用您的方法返回类型的 EventHandler 签名将始终为“无效”。 最常见的事件引发是触发和忘记操作(例如 button_Click),我们可能不关心操作的结果,但是当我们确实需要一些反馈时,总有一种解决方法,因为订阅者方法可以将这些结果保存到发布者的属性中; 它只需要转换“sender”对象来获取发布者的实例[这应该没问题,除非订阅者依赖于发布者(场景指出,我们不需要事件开始!)]。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM