Warum benötigen wir das Schlüsselwort "event", um Ereignisse zu definieren?

108

Ich verstehe nicht, warum wir das Schlüsselwort "event" beim Definieren von Ereignissen benötigen, wenn wir dasselbe tun können, ohne das Schlüsselwort "event" zu verwenden, nur indem wir die Delegaten verwenden.

z.B

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

Wenn ich hier das Schlüsselwort "event" aus der zweiten Zeile entferne, kann ich das Ereignis auch durch Aufrufen des Delegaten auslösen. Kann mir bitte jemand sagen, warum dieses Ereignis-Keyword benötigt wird?

Teenup
quelle
OK, wenn Sie das Ereignis-Schlüsselwort nicht verwenden. Jeder, der mit dem Klassenobjekt auf dieses Ereignis zugreifen kann, setzt es wie objClass.SelectedIndexChanged = null auf NULL. Dadurch wird Ihr zugrunde liegender Code zum Absturz gebracht. Das Schlüsselwort event erzwingt, dass der Benutzer mit + = etwas Ähnliches wie das Delegieren zuweist.
Sumit Kapadia

Antworten:

141

Feldähnliche Ereignisse und öffentliche Felder von Delegatentypen sehen ähnlich aus, sind jedoch tatsächlich sehr unterschiedlich.

Ein Ereignis ist im Grunde wie eine Eigenschaft - es ist ein Paar von Add / Remove-Methoden (anstelle des get / set einer Eigenschaft). Wenn Sie ein feldähnliches Ereignis deklarieren (dh ein Ereignis, bei dem Sie die Add / Remove-Bits nicht selbst angeben), wird ein öffentliches Ereignis und ein privates Hintergrundfeld erstellt. Auf diese Weise können Sie das Ereignis privat auslösen, jedoch ein öffentliches Abonnement zulassen. Mit einem öffentlichen Delegiertenfeld kann jeder die Ereignishandler anderer Personen entfernen, das Ereignis selbst auslösen usw. - dies ist eine Kapselungskatastrophe.

Weitere Informationen zu Veranstaltungen (und Delegierten) finden Sie in meinem Artikel zu diesem Thema . (Irgendwann muss ich dies für C # 4 aktualisieren, wodurch sich feldähnliche Ereignisse nur geringfügig ändern. Der Kern ist jedoch immer noch korrekt.)

Jon Skeet
quelle
17
Dies ist tausendmal besser als die offizielle einzeilige Erklärung von MSDN: "Das Ereignisschlüsselwort wird verwendet, um ein Ereignis in einer Publisher-Klasse zu deklarieren."
Cowlinator
36

Das Ereignis-Schlüsselwort macht drei verschiedene Dinge:

  1. Sie können ein Ereignis in einer Schnittstelle definieren, obwohl Sie keine regulären Felder in Schnittstellen definieren können.
  2. Es ändert die Sichtbarkeit der Operatoren =und ()(Zuweisung und Aufruf) in privat, sodass nur die enthaltende Klasse das Ereignis aufrufen oder alle darin enthaltenen Methoden überschreiben kann. Die Operatoren -=und +=können weiterhin für ein Ereignis von außerhalb der Klasse aufgerufen werden, die es definiert (sie erhalten den Zugriffsmodifikator, den Sie neben dem Ereignis geschrieben haben).
  3. Sie können auch die Art -=und Weise überschreiben und +=sich bei Ereignissen verhalten.
Eiche
quelle
2
Sie> MSDN. Vielen Dank.
M. Azyoksul
26

Die anderen Antworten sind in Ordnung; Ich möchte nur noch etwas hinzufügen, über das ich nachdenken kann.

Ihre Frage lautet: "Warum brauchen wir Ereignisse, wenn wir Felder vom Typ Delegat haben?" Ich würde diese Frage erweitern: Warum benötigen Sie Methoden, Eigenschaften, Ereignisse, Instanzkonstruktoren oder Finalizer, wenn Sie Felder vom Typ Delegat haben? Warum benötigen Sie etwas anderes als Felder, die Werte und Delegaten in einem Typ enthalten? Warum nicht einfach sagen

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

?

Sie müssen nicht brauchen Methoden, Eigenschaften oder Ereignisse. Wir geben Ihnen diese Informationen, da die Entwurfsmuster für Methoden, Eigenschaften und Ereignisse wichtig und nützlich sind und eine standardisierte, dokumentierte und klare Möglichkeit verdienen, sie in der Sprache zu implementieren.

Eric Lippert
quelle
4
Beeindruckend! Es erinnert daran, warum ich c # liebe! Von allen Sprachen, mit denen ich gearbeitet habe, bietet es die richtige Balance zwischen Kompaktheit, Flexibilität und lesbarer Semantik. Die einzige vergleichbare Sprache ist Object Pascal.
ATL_DEV
1
@ATL_DEV: Dafür gibt es einen Grund. Der Architekt der C # -Sprache, Anders Hejlsberg, war zuvor der Architekt von Delphi, einer Sprache, die auf Object Pascal basiert.
Eric Lippert
9

Es wird teilweise benötigt, da eventdie Kapselung unterbrochen wird, wenn Sie das Schlüsselwort weglassen . Wenn es sich nur um einen öffentlichen Multicast-Delegaten handelt, kann jeder ihn aufrufen, auf null setzen oder manipulieren. Wenn eine aufgerufene Klasse MailNotifierexistiert und ein Ereignis aufgerufen wurde MailReceived, ist es für andere Typen nicht sinnvoll, dieses Ereignis per Aufruf mailNotifier.MailReceived()auszulösen.

Auf der anderen Seite können Sie sich nur in feldähnliche Ereignisse des Typs einmischen und diesen aufrufen, der sie definiert hat.

Wenn Sie Ihren Ereignisaufruf privat halten möchten, hindert Sie nichts daran, Folgendes zu tun:

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

... aber das ist eine ganze Menge Code, nur um (mehr oder weniger) das zu tun, was feldähnliche Ereignisse uns bereits geben.

Mark Simpson
quelle
Es wäre auch schwieriger für Dinge wie Designer, sie zu verwenden ... Sie würden sich im Grunde auf Namenskonventionen für Methoden verlassen, anstatt öffentliche Metadaten zu haben, die sagen "Dies ist ein Ereignis".
Jon Skeet
3

Ereignisse haben deutliche Vorteile gegenüber Delegatenfeldern. Ereignisse können in Schnittstellen im Gegensatz zu Feldern definiert werden, wodurch dem Code eine Abstraktion hinzugefügt wird, und noch wichtiger: Ereignisse können nur innerhalb der definierenden Klasse aufgerufen werden. In Ihrem Fall könnte jeder das Ereignis aufrufen und möglicherweise Ihren Code zerstören.

Weitere Informationen finden Sie in diesem Blogbeitrag .

Femaref
quelle
2
Sie sollten Ereignisse und Delegaten nicht wirklich vergleichen - vergleichen Sie Ereignisse und öffentliche Felder mit einem Delegatentyp . Und nein, das Framework erfordert keine Ereignisse mit dieser Signatur. Sie können ein Ereignis eines beliebigen Delegatentyps erstellen.
Jon Skeet
3

Delegat ist ein Referenztyp. Es erbt MulticastDelegate . Veranstaltung ist ein Modifikator. Veranstaltungist ein spezieller Modifikator für Delegierte. Es ändert einige Funktionen / Methoden, z. B. die Invoke-Methode. Nach dem Ändern durch ein Modifikatorereignis wird eine Delegateninstanz zu einem neuen Konzept "Ereignis". Event ist also nur ein modifizierter Delegat. Sie können die Referenz nicht direkt ändern oder ein Ereignis außerhalb der Klasse aufrufen, in der das Ereignis definiert wurde. Sie können jedoch die Referenz ändern oder eine normale Delegateninstanz aufrufen. Event bietet zusätzlichen Schutz, sodass Event mehr Sicherheitsfunktionen bietet. Wenn Sie sich außerhalb der Klasse befinden, in der das Ereignis definiert wurde, können Sie zwei Arten von Operationen für das Ereignis ausführen: "+ =" und "- =". Sie können jedoch auf alle öffentlichen Felder, Eigenschaften, Methoden usw. einer normalen Delegateninstanz zugreifen. Hier ist ein Beispiel:

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
        }
    }
}
Wuyin Lyu
quelle