Pourquoi avons-nous besoin du mot-clé «événement» lors de la définition des événements?

109

Je ne comprends pas pourquoi nous avons besoin du mot-clé "event" lors de la définition des événements, alors que nous pouvons faire la même chose sans utiliser le mot-clé "event", simplement en utilisant les délégués.

par exemple

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

Ici, si je supprime le mot-clé "event" de la deuxième ligne, je peux également déclencher l'événement en appelant le délégué. Quelqu'un peut-il me dire pourquoi ce mot clé d'événement est-il nécessaire?

adolescent
la source
ok si vous n'utilisez pas de mot-clé d'événement, quiconque peut accéder à cet événement en utilisant un objet de classe, définissez-le sur NULL comme objClass.SelectedIndexChanged = null. cela plantera votre code sous-jacent. Le mot clé event oblige l'utilisateur à attribuer quelque chose de similaire à déléguer en utilisant + =.
Sumit Kapadia

Réponses:

142

Les événements de type champ et les champs publics des types de délégués se ressemblent, mais sont en fait très différents.

Un événement est fondamentalement comme une propriété - c'est une paire de méthodes d'ajout / suppression (au lieu du get / set d'une propriété). Lorsque vous déclarez un événement de type champ (c'est-à-dire un événement où vous ne spécifiez pas vous-même les bits d'ajout / de suppression), un événement public est créé et un champ de sauvegarde privé. Cela vous permet de déclencher l'événement en privé, mais d'autoriser l'abonnement public. Avec un champ de délégué public, n'importe qui peut supprimer les gestionnaires d'événements d'autres personnes, déclencher l'événement lui-même, etc. - c'est un désastre d'encapsulation.

Pour en savoir plus sur les événements (et les délégués), lisez mon article sur ce sujet . (À un moment donné, je dois mettre à jour cela pour C # 4, ce qui change très légèrement les événements de type champ. L'essentiel est toujours correct.)

Jon Skeet
la source
17
C'est mille fois mieux que l'explication officielle en une ligne de MSDN: "Le mot clé event est utilisé pour déclarer un événement dans une classe d'éditeur."
cowlinator
37

Le mot-clé événement fait 3 choses différentes:

  1. Vous pouvez définir un événement dans une interface, même si vous ne pouvez pas définir de champs réguliers dans les interfaces.
  2. Il modifie la visibilité des opérateurs =et ()(affectation et appel) en private, de sorte que seule la classe contenant peut invoquer l'événement ou remplacer toutes les méthodes qu'il contient. Les opérateurs -=et +=peuvent toujours être appelés sur un événement depuis l'extérieur de la classe qui le définit (ils obtiennent le modificateur d'accès que vous avez écrit à côté de l'événement).
  3. Vous pouvez également remplacer la manière -=et vous +=comporter lors d'événements.
Chêne
la source
2
vous> MSDN. Merci.
M. Azyoksul
26

Les autres réponses sont très bien; Je voudrais juste ajouter quelque chose à quoi réfléchir.

Votre question est "pourquoi avons-nous besoin d'événements lorsque nous avons des champs de type délégué?" Je voudrais étendre cette question: pourquoi avez-vous besoin de méthodes, de propriétés, d'événements, de constructeurs d'instances ou de finaliseurs si vous avez des champs de type délégué? Pourquoi avez-vous besoin d' autre chose que des champs contenant des valeurs et des délégués dans un type? Pourquoi ne pas simplement dire

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

?

Vous n'avez pas besoin de méthodes, de propriétés ou d'événements. Nous vous donnons cela parce que les modèles de conception de méthode, de propriété et d'événement sont importants et utiles, et méritent d'avoir une manière standard, documentée et claire de les implémenter dans le langage.

Eric Lippert
la source
4
Hou la la! Cela me rappelle pourquoi j'aime le c #! De tous les langages avec lesquels j'ai travaillé, il a le bon équilibre entre compacité, flexibilité et sémantique lisible. Le seul langage comparable est Object Pascal.
ATL_DEV
1
@ATL_DEV: Il y a une raison à cela. L'architecte du langage C #, Anders Hejlsberg, était auparavant l'architecte de Delphi, qui était un langage basé sur Object Pascal.
Eric Lippert
9

C'est en partie nécessaire car si vous omettez le eventmot - clé, cela rompt l'encapsulation. S'il ne s'agit que d'un délégué de multidiffusion public, n'importe qui peut l'invoquer, le définir sur null ou le falsifier. Si une classe appelée MailNotifierexiste et qu'elle a un événement appelé MailReceived, cela n'a aucun sens pour les autres types de pouvoir déclencher cet événement via l'appel mailNotifier.MailReceived();

D'un autre côté, vous ne pouvez manipuler et invoquer des événements «comme un champ» que du type qui l'a défini.

Si vous voulez garder votre invocation d'événement privée, rien ne vous empêche de faire quelque chose comme ceci:

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

... mais c'est tout un tas de code juste pour (plus ou moins) faire ce que les événements de type champ nous donnent déjà.

Mark Simpson
la source
Ce serait aussi plus difficile pour des choses comme les concepteurs à utiliser ... vous vous fiez essentiellement à des conventions de dénomination pour les méthodes, au lieu d'avoir des métadonnées publiques disant "c'est un événement".
Jon Skeet
3

Les événements présentent des avantages distincts par rapport aux champs délégués. Les événements peuvent être définis dans les interfaces contrairement aux champs, en ajoutant une abstraction au code, et plus important encore: les événements ne peuvent être appelés qu'à l'intérieur de la classe de définition. Dans votre cas, n'importe qui pourrait appeler l'événement, détruisant éventuellement votre code.

Consultez ce billet de blog pour plus d'informations.

Femaref
la source
2
Vous ne devriez pas vraiment comparer des événements et des délégués - comparez des événements et des champs publics avec un type de délégué . Et non, le framework ne nécessite pas d'événements pour avoir cette signature. Vous pouvez créer un événement de n'importe quel type de délégué que vous souhaitez.
Jon Skeet
3

délégué est un type de référence. Il hérite de MulticastDelegate . l'événement est un modificateur. un événementest un modificateur spécial pour le délégué. Il modifie certaines fonctions / méthodes d'accessibilité, par exemple la méthode Invoke. Après avoir été modifiée par un événement de modificateur, une instance de délégué devient un nouveau concept «Événement». Donc, Event n'est qu'un délégué modifié. Vous ne pouvez pas modifier directement la référence ou appeler un événement en dehors de la classe dans laquelle l'événement a été défini, mais vous pouvez modifier la référence ou appeler une instance de délégué normale. L'événement fournit une protection supplémentaire, de sorte que l'événement ait plus de fonctionnalités de sécurité. Lorsque vous êtes en dehors de la classe où l'événement a été défini, vous êtes autorisé à effectuer deux types d'opérations sur l'événement, "+ =" et "- =". Mais vous pouvez accéder à tous les champs publics, propriétés, méthodes, etc. d'une instance de délégué normale. Voici un exemple:

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
la source