¿Por qué necesitamos la palabra clave "evento" al definir eventos?

109

No entiendo por qué necesitamos la palabra clave "evento" al definir eventos, cuando podemos hacer lo mismo sin usar la palabra clave "evento", simplemente usando los delegados.

p.ej

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

Aquí, si elimino la palabra clave "evento" de la segunda línea, también puedo generar el evento invocando al delegado. ¿Alguien puede decirme por qué se necesita esta palabra clave de evento?

adolescente
fuente
ok, si no usa la palabra clave de evento, cualquiera que pueda acceder a ese evento usando el objeto de clase, configúrelo en NULL como objClass.SelectedIndexChanged = null. esto bloqueará su código subyacente. La palabra clave de evento obliga al usuario a asignar algo similar a delegar usando + =.
Sumit Kapadia

Respuestas:

142

Los eventos similares a campos y los campos públicos de los tipos de delegados se ven similares, pero en realidad son muy diferentes.

Un evento es fundamentalmente como una propiedad: es un par de métodos de agregar / quitar (en lugar del get / set de una propiedad). Cuando declaras un evento similar a un campo (es decir, uno en el que no especificas agregar / eliminar bits tú mismo), se crea un evento público y un campo de respaldo privado. Esto le permite plantear el evento de forma privada, pero permite la suscripción pública. Con un campo de delegado público, cualquiera puede eliminar los controladores de eventos de otras personas, generar el evento ellos mismos, etc. Es un desastre de encapsulación.

Para más información sobre eventos (y delegados) lea mi artículo sobre este tema . (En algún momento, necesito actualizar esto para C # 4, que cambia ligeramente los eventos tipo campo. Sin embargo, la esencia sigue siendo correcta).

Jon Skeet
fuente
17
Esto es mil veces mejor que la explicación oficial de una línea de MSDN: "La palabra clave de evento se usa para declarar un evento en una clase de editor".
cowlinator
37

La palabra clave de evento hace 3 cosas diferentes:

  1. Puede definir un evento en una interfaz, aunque no pueda definir campos regulares en las interfaces.
  2. Cambia la visibilidad de los operadores =y ()(asignación e invocación) a privado, de modo que solo la clase que lo contiene pueda invocar el evento o anular todos los métodos que contiene. Los operadores -=y +=aún se pueden invocar en un evento desde fuera de la clase que lo define (obtienen el modificador de acceso que escribió junto al evento).
  3. También puede anular la forma -=y el +=comportamiento de los eventos.
Roble
fuente
2
usted> MSDN. Gracias.
M. Azyoksul
26

Las otras respuestas están bien; Solo me gustaría agregar algo más en lo que pensar.

Su pregunta es "¿por qué necesitamos eventos cuando tenemos campos de tipo delegado?" Ampliaría esa pregunta: ¿por qué necesita métodos, propiedades, eventos, constructores de instancias o finalizadores si tiene campos de tipo delegado? ¿Por qué necesita algo más que campos que contienen valores y delegados en un tipo? Por qué no decir

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

?

No necesitas métodos, propiedades o eventos. Le brindamos todo eso porque el método, la propiedad y los patrones de diseño de eventos son importantes y útiles, y merecen tener una forma estándar, documentada y clara de implementarlos en el lenguaje.

Eric Lippert
fuente
4
¡Guauu! ¡Recuerda por qué amo C #! De todos los lenguajes con los que he trabajado, tiene el equilibrio adecuado entre compacidad, flexibilidad y semántica legible. El único lenguaje comparable es Object Pascal.
ATL_DEV
1
@ATL_DEV: Hay una razón para eso. El arquitecto del lenguaje C #, Anders Hejlsberg, fue anteriormente el arquitecto de Delphi, que era un lenguaje basado en Object Pascal.
Eric Lippert
9

Es en parte necesario porque si omite la eventpalabra clave, se rompe la encapsulación. Si es solo un delegado de multidifusión público, cualquiera puede invocarlo, configurarlo como nulo o alterarlo. Si MailNotifierexiste una clase llamada y tiene un evento llamado MailReceived, no tiene sentido que otros tipos puedan disparar ese evento mediante una llamada mailNotifier.MailReceived();

Por otro lado, solo puede entrometerse e invocar eventos de 'campo como' del tipo que lo definió.

Si desea mantener privada la invocación de su evento, no hay nada que le impida hacer algo como esto:

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

... pero eso es una gran cantidad de código solo para (más o menos) hacer lo que los eventos de campo ya nos brindan.

Mark Simpson
fuente
También sería más difícil de usar para cosas como los diseñadores ... básicamente dependería de las convenciones de nomenclatura para los métodos, en lugar de que haya metadatos públicos que digan "esto es un evento".
Jon Skeet
3

Los eventos tienen distintas ventajas en comparación con los campos delegados. Los eventos se pueden definir en interfaces en contraste con los campos, agregando abstracción al código y, lo que es aún más importante: los eventos solo se pueden llamar desde dentro de la clase de definición. En su caso, cualquiera podría llamar al evento, posiblemente destruyendo su código.

Consulte esta publicación de blog para obtener más información.

Femaref
fuente
2
Realmente no debería estar comparando eventos y delegados ; compare eventos y campos públicos con un tipo de delegado . Y no, el marco no requiere que los eventos tengan esa firma. Puede crear un evento de cualquier tipo de delegado que desee.
Jon Skeet
3

delegado es un tipo de referencia. Hereda MulticastDelegate . evento es un modificador. eventoes un modificador especial para delegado. Modifica la accesibilidad de alguna función / método, por ejemplo, Invocar método. Una vez modificada por evento modificador, una instancia de delegado se convierte en un nuevo concepto "Evento". Entonces, Event es solo un delegado modificado. No puede cambiar la referencia directamente o invocar un Evento fuera de la clase donde se definió el Evento, pero puede cambiar la referencia o invocar una instancia de delegado normal. Event proporciona protección adicional, de modo que Event tiene más funciones de seguridad. Cuando está fuera de la clase donde se definió el evento, se le permite realizar dos tipos de operaciones en el evento, "+ =" y "- =". Pero puede acceder a todos los campos públicos, propiedades, métodos, etc. de una instancia de delegado normal. He aquí un ejemplo:

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
fuente