簡體   English   中英

為什么我們在定義事件時需要“事件”關鍵字?

[英]Why do we need the "event" keyword while defining events?

我不明白為什么我們在定義事件時需要event關鍵字,而我們可以在不使用event關鍵字的情況下僅通過使用委托來做同樣的事情。

例如:

public delegate void CustomEventHandler(int a, string b);
public event CustomEventHandler customEvent;
customEvent += new CustomEventHandler(customEventHandler);
customEvent(1,"a"); // Raising the event

在這里,如果我從第二行中刪除event關鍵字,那么我也可以通過調用委托來引發事件。

誰能告訴我為什么需要這個event關鍵字?

類似字段的事件和委托類型的公共字段看起來相似,但實際上卻有很大的不同。

事件從根本上來說就像一個屬性-它是一對添加/刪除方法(而不是屬性的獲取/設置)。 當您聲明類似字段的事件(即您自己不指定添加/刪除位的事件)時,將創建一個公共事件和一個私有后備字段。 這使您可以私下引發事件,但可以公開訂閱。 使用公共委托字段, 任何人都可以刪除其他人的事件處理程序,自行引發事件,等等-這是封裝災難。

有關事件(和代表)的更多信息,請閱讀有關該主題的文章 (在某些時候,我需要針對C#4對此進行更新,它會稍微更改類似字段的事件。盡管如此,其要點仍然是正確的。)

event關鍵字執行3項不同的操作:

  1. 即使不能在接口中定義常規字段,也可以在接口中定義事件。
  2. 它將=()運算符(賦值和調用)的可見性更改為private,以便只有包含類可以調用該事件或覆蓋該事件中包含的所有方法。 -=+=運算符仍然可以在定義事件的類之外的事件上調用(它們獲得您在事件旁邊編寫的access修飾符)。
  3. 您還可以覆蓋-=+=在事件上的行為方式。

其他答案很好。 我只想添加其他要考慮的內容。

您的問題是“為什么當我們有委托類型的字段時我們需要事件?” 我會擴展這個問題:如果您有委托類型的字段,為什么需要方法,屬性,事件,實例構造函數或終結器? 為什么你需要比這包含一個類型的值和代表領域的其他東西嗎? 為什么不只是說

class C
{
    private int z;
    public readonly Func<int, int> M = (int x)=>{ return x+z; }
    // ... and so on
}

不需要方法,屬性或事件。 我們之所以能提供這些東西,是因為方法,屬性和事件設計模式非常重要且有用,並且應該有一種標准的,有文檔的,清晰的方法來用語言實現它們。

之所以需要這樣做,是因為如果省略event關鍵字,則會破壞封裝。 如果它只是一個公共多播委托,則任何人都可以調用它,將其設置為null或篡改它。 如果存在一個名為MailNotifier的類,並且有一個名為MailReceived的事件,則其他類型無法通過調用mailNotifier.MailReceived()來觸發該事件;

另一方面,您只能從定義它的類型中介入並調用“類似字段”事件。

如果您想將事件調用設為不公開,則沒有什么可以阻止您執行以下操作:

public class MyClassWithNonFieldLikeEvent
{
   private CustomEventHandler m_delegate;

   public void Subscribe(CustomEventHandler handler) 
   {
      m_delegate += handler;        
   }

   public void Unsubscribe(CustomEventHandler handler)
   {          
      m_delegate -= handler;
   }

   private void DoSomethingThatRaisesEvent()
   {
      m_delegate.Invoke(...);
   }       
}

...但這就是(或多或少)執行類似字段的事件已經給我們提供的全部代碼。

與委托字段相比,事件具有明顯的優勢。 可以在與字段相反的接口中定義事件,從而為代碼添加抽象,甚至更重要的是:只能從定義類內部調用事件。 就您而言,任何人都可以調用該事件,有可能破壞您的代碼。

有關更多信息,請參見此博客文章

委托是引用類型。 它繼承了MulticastDelegate 事件是一個修飾符。 事件是委托的特殊修飾符。 它修改了某些功能/方法的可訪問性,例如調用方法。 通過修飾符事件修改后,委托實例成為新概念“事件”。 因此,Event只是一個修改的委托。 您不能在定義事件的類之外直接更改引用或調用事件,但是可以更改引用或調用普通的委托實例。 事件提供了額外的保護,因此事件具有更多的安全功能。 當您在定義事件的類之外時,可以對事件執行兩種操作:“ + =”和“-=”。 但是您可以訪問普通委托實例的所有公共字段,屬性,方法等。 這是一個例子:

namespace DelegateEvent
{
    //the following line behave as a class. It is indeed a reference type
    public delegate void MyDelegate(string inputs);

    //The following line is illegal. It can only be an instance. so it cannot be directly under namespace
    //public event MyDelegate MyEvent;


    public class MyClassA
    {
        public event MyDelegate MyEventA;
        public MyDelegate MyDelegateA;


        System.Threading.ManualResetEvent MyResetEvent = new System.Threading.ManualResetEvent(false);
        public void TryToDoSomethingOnMyDelegateA()
        {
            if (MyDelegateA != null)
            {
                //User can assecc all the public methods.
                MyDelegateA("I can invoke detegate in classA");         //invoke delegate
                MyDelegateA.Invoke("I can invoke detegate in classA");  //invoke delegate
                IAsyncResult result = MyDelegateA.BeginInvoke("I can invoke detegate in classA", MyAsyncCallback, MyResetEvent);    //Async invoke
                //user can check the public properties and fields of delegate instance
                System.Reflection.MethodInfo delegateAMethodInfo = MyDelegateA.Method;

                MyDelegateA = testMethod;                   //reset reference
                MyDelegateA = new MyDelegate(testMethod);   //reset reference
                MyDelegateA = null;                         //reset reference


                MyDelegateA += testMethod;                  //Add delegate
                MyDelegateA += new MyDelegate(testMethod);  //Add delegate
                MyDelegateA -= testMethod;                  //Remove delegate
                MyDelegateA -= new MyDelegate(testMethod);  //Remove delegate
            }
        }

        public void TryToDoSomethingOnMyEventA()
        {
            if (MyEventA != null)
            {
                MyEventA("I can invoke Event in classA");           //invoke Event
                MyEventA.Invoke("I can invoke Event in classA");    //invoke Event
                IAsyncResult result = MyEventA.BeginInvoke("I can invoke Event in classA", MyAsyncCallback, MyResetEvent);      //Async invoke
                //user can check the public properties and fields of MyEventA
                System.Reflection.MethodInfo delegateAMethodInfo = MyEventA.Method;


                MyEventA = testMethod;                   //reset reference
                MyEventA = new MyDelegate(testMethod);   //reset reference
                MyEventA = null;                         //reset reference


                MyEventA += testMethod;                  //Add delegate
                MyEventA += new MyDelegate(testMethod);  //Add delegate
                MyEventA -= testMethod;                  //Remove delegate
                MyEventA -= new MyDelegate(testMethod);  //Remove delegate
            }
        }

        private void MyAsyncCallback(System.IAsyncResult result)
        {
            //user may do something here
        }
        private void testMethod(string inputs)
        {
            //do something
        }

    }
    public class MyClassB
    {
        public MyClassB()
        {
            classA = new MyClassA();
        }
        public MyClassA classA;
        public string ReturnTheSameString(string inputString)
        {
            return inputString;
        }


        public void TryToDoSomethingOnMyDelegateA()
        {
            if (classA.MyDelegateA != null)
            {
                //The following two lines do the same job --> invoke the delegate instance
                classA.MyDelegateA("I can invoke delegate which defined in class A in ClassB");
                classA.MyDelegateA.Invoke("I can invoke delegate which defined in class A in ClassB");
                //Async invoke is also allowed

                //user can check the public properties and fields of delegate instance
                System.Reflection.MethodInfo delegateAMethodInfo = classA.MyDelegateA.Method;

                classA.MyDelegateA = testMethod;                   //reset reference
                classA.MyDelegateA = new MyDelegate(testMethod);   //reset reference
                classA.MyDelegateA = null;                         //reset reference


                classA.MyDelegateA += testMethod;                  //Add delegate
                classA.MyDelegateA += new MyDelegate(testMethod);  //Add delegate
                classA.MyDelegateA -= testMethod;                  //Remove delegate
                classA.MyDelegateA -= new MyDelegate(testMethod);  //Remove delegate

            }

        }
        public void TryToDoSomeThingMyEventA()
        {
            //check whether classA.MyEventA is null or not is not allowed
            //Invoke classA.MyEventA is not allowed
            //Check properties and fields of classA.MyEventA is not allowed
            //reset classA.MyEventA reference is not allowed

            classA.MyEventA += testMethod;                  //Add delegate
            classA.MyEventA += new MyDelegate(testMethod);  //Add delegate
            classA.MyEventA -= testMethod;                  //Remove delegate
            classA.MyEventA -= new MyDelegate(testMethod);  //Remove delegate
        }

        private void testMethod(string inputs)
        {
            //do something here
        }
    }
}

使用網站 sharplab.io 您實際上可以反編譯“event”關鍵字的作用。

例如下面的程序:

using System;
using System.ComponentModel;
public class C {
    
    public event EventHandler TestChanged;
    
    public void M() {
    }
}

反編譯為以下內容:

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Threading;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
public class C
{
    [CompilerGenerated]
    private EventHandler m_TestChanged;

    public event EventHandler TestChanged
    {
        [CompilerGenerated]
        add
        {
            EventHandler eventHandler = this.TestChanged;
            while (true)
            {
                EventHandler eventHandler2 = eventHandler;
                EventHandler value2 = (EventHandler)Delegate.Combine(eventHandler2, value);
                eventHandler = Interlocked.CompareExchange(ref this.TestChanged, value2, eventHandler2);
                if ((object)eventHandler == eventHandler2)
                {
                    break;
                }
            }
        }
        [CompilerGenerated]
        remove
        {
            EventHandler eventHandler = this.TestChanged;
            while (true)
            {
                EventHandler eventHandler2 = eventHandler;
                EventHandler value2 = (EventHandler)Delegate.Remove(eventHandler2, value);
                eventHandler = Interlocked.CompareExchange(ref this.TestChanged, value2, eventHandler2);
                if ((object)eventHandler == eventHandler2)
                {
                    break;
                }
            }
        }
    }

    public void M()
    {
    }
}

所以你完全可以寫出和上面一樣的代碼,只是很啰嗦,容易出錯。 event 關鍵字會為您處理這個問題。 與許多其他關鍵字(如 async 等)相同。所以它實際上只是語法糖,僅此而已。

為了好玩,嘗試使用 sharplab.io 反編譯其他關鍵字以查看。 這是一次很棒的學習經歷。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM