簡體   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