2010-06-23 15 views
21

Esto me parece extraño: VB.NET maneja la verificación nula implícitamente a través de su palabra clave RaiseEvent. Parece elevar considerablemente la cantidad de repeticiones en torno a los eventos y no veo qué beneficio proporciona.¿Por qué C# le exige que escriba un cheque nulo cada vez que activa un evento?

Estoy seguro de que los diseñadores de idiomas tenían una buena razón para hacer esto ... pero tengo curiosidad si alguien sabe por qué.

Respuesta

18

Es ciertamente un punto de enojo

Al escribir código que accede a un evento de campo como dentro de una clase, usted es realmente acceder al campo mismo (módulo algunos cambios en C# 4; no vamos a ir allí por el momento).

Por lo tanto, las opciones serían:

  • de casos especiales invocaciones por eventos de campo como para que ellos no se refieren en realidad al campo directamente, sino que añade un envoltorio
  • manija todo delegar invocaciones de manera diferente, de modo que:

    Action<string> x = null; 
    x(); 
    

    no arrojaría una excepción.

Por supuesto, para los delegados no nulos (eventos) y las dos opciones plantean un problema:

Func<int> x = null; 
int y = x(); 

En caso de que silenciosamente devolver 0? (El valor predeterminado es int). ¿O está enmascarando un error (más probable)? Sería algo inconsistente hacer que ignore silenciosamente el hecho de que está tratando de invocar a un delegado nulo. Sería aún más extraño en este caso, que no utiliza el azúcar sintáctica C# 's:

Func<int> x = null; 
int y = x.Invoke(); 

Básicamente las cosas se vuelven difíciles e inconsistente con el resto de la lengua casi cualquier cosa que hagas. No me gusta tampoco, pero no estoy seguro de qué solución práctica, pero consistente podría ser ...

+4

Francamente, lo veo como * muy * molestia menor. Prefiero una excepción sobre los errores silenciosos. – ChaosPandion

+0

Aunque, a veces, cuando veo * "frameworks" * lanzando muchos 'NullReferenceExceptions' mi resolución se debilita. – ChaosPandion

+0

abundancias inconstancias. Todavía no puedo llamar 'Nullable .HasValue' sin pensar" ¡no puedes acceder a una propiedad con un valor que podría ser nulo! " –

3

no se sabe muy bien por qué se hace esto, pero hay una variación de un Null Object pattern específicamente para delegados:

private event EventHandler Foo = (sender, args) => {}; 

esta manera se puede invocar libremente sin tener que Foo comprobación de null.

+2

'evento público manejador de sucesos Foo = delegado {};' es más limpio de la OMI. Aunque personalmente no me gusta este patrón. –

3

Porque RaiseEvent tiene un poco de sobrecarga.

Siempre hay una compensación entre el control y la facilidad de uso.

  • VB.Net: facilidad de uso,
  • C#: más control como VB
  • C++: aún más control, menos orientación, más fácil de disparar en el pie
+0

¿Qué gastos generales? ¿Buscando 'null'? Eso es ciertamente mucho menos que una llamada a un método ... –

+0

Estaba pensando en la sintaxis de Handles de VB.Net. Pero tienes razón, no mucho, editado. – GvS

12

que por lo general evitar este declarando nuestros eventos como éste:

public event EventHandler<FooEventArgs> Foo = delegate { }; 

esto tiene dos ventajas. El primero es que no tenemos cheque para null. La segunda es que se evita el problema de la sección crítica que es omnipresente en el típico caso de fuego:

// old, busted code that happens to work most of the time since 
// a lot of code never unsubscribes from events 
protected virtual void OnFoo(FooEventArgs e) 
{ 
    // two threads, first checks for null and succeeds and enters 
    if (Foo != null) { 
     // second thread removes event handler now, leaving Foo = null 
     // first thread resumes and crashes. 
     Foo(this, e); 
    } 
} 

// proper thread-safe code 
protected virtual void OnFoo(FooEventArgs e) 
{ 
    EventHandler<FooEventArgs> handler = Foo; 
    if (handler != null) 
     handler(this, e); 
} 

Pero con la inicialización automática de Foo a un delegado de vacío, nunca hay ninguna comprobación necesaria y el código es automáticamente thread- segura y fácil de leer para el arranque:

protected virtual void OnFoo(FooEventArgs e) 
{ 
    Foo(this, e); // always good 
} 

con perdón de Pat Morita en el Karate Kid, "la mejor manera de evitar nula no se tiene."

En cuanto al por qué, C# no te mima tanto como VB. Aunque la palabra clave de evento oculta la mayoría de los detalles de implementación de multicast delegates, le da un control más fino que VB.

+0

+1: O más convincentemente si inicia el evento a un delegado que hace algo práctico (si el escenario lo requiere, por supuesto), entonces nunca necesitaría hacer el cheque. Sin embargo, es un buen punto para evitar la condición de carrera en los escenarios de subprocesos. Esta es la mejor respuesta en mi opinión ... incluso mejor que la mía :) –

+0

¡Es todo diversión y juegos hasta que intentes convertir lo anterior en Visual Basic y descubras que no admite delegados anónimos ...! – NibblyPig

+1

@SLC: Sin embargo, esto es menos problemático para Visual Basic, porque la palabra clave RaiseEvent maneja correctamente la verificación 'null'. –

0

La razón realmente se reduce a C# que le da más control. En C# no tiene que hacer el control null si así lo desea. En el siguiente código MyEvent nunca puede ser null ¿por qué molestarse en hacer el cheque?

public class EventTest 
{ 
    private event EventHandler MyEvent = delegate { Console.WriteLine("Hello World"); } 

    public void Test() 
    { 
    MyEvent(this, new EventArgs()); 
    } 
} 
+0

¿Hay alguna razón por la que no podrían haber hecho * ambas *? Esto me parece un PITA bastante común que me sorprende que no haya una sintaxis alternativa que evite la comprobación nula explícita. –

8

debe tener en cuenta qué código se requeriría si la configuración de la instalación de cañerías para elevar el caso, en primer lugar sería costoso (como SystemEvents) o en la preparación de los argumentos del evento sería costoso (como el evento Paint)

El estilo de manejo de eventos de Visual Basic no le permite posponer el costo de admitir dicho evento. No puede anular los accesorios de agregar/quitar para retrasar la instalación de la costosa instalación de tuberías. Y no puede descubrir que no haya suscriptores de eventos suscriptos, por lo que grabar los ciclos para preparar los argumentos del evento es una pérdida de tiempo.

No es un problema en C#. Compensación clásica entre conveniencia y control.

+0

+1 - No puedo creer que no haya notado esto cuando lo pregunté, lol. –

5

Los métodos de extensión proporcionan una manera muy buena para evitar esto. Considere el siguiente código:

static public class Extensions 
{ 
    public static void Raise(this EventHandler handler, object sender) 
    { 
     Raise(handler, sender, EventArgs.Empty); 
    } 

    public static void Raise(this EventHandler handler, object sender, EventArgs args) 
    { 
     if (handler != null) handler(sender, args); 
    } 

    public static void Raise<T>(this EventHandler<T> handler, object sender, T args) 
     where T : EventArgs 
    { 
     if (handler != null) handler(sender, args); 
    } 
} 

Ahora usted puede simplemente hacer esto:

class Test 
{ 
    public event EventHandler SomeEvent; 

    public void DoSomething() 
    { 
     SomeEvent.Raise(this); 
    } 
} 

Sin embargo, como otros ya mencionados, que deben ser conscientes de la posible condición de carrera en escenarios multi-hilo.

+0

Eso está bien, pero debe copiar el evento antes de subirlo. (Vea el comentario de Eric Lippert algunas publicaciones ...) –

+0

Creo que Eric Lippert se refiere a otra condición de carrera que aún puede ocurrir, incluso con la copia local. Encontré una publicación de blog de él con respecto a ese tema: http://blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx El problema es que el hilo- los eventos seguros requieren una consideración cuidadosa y, aunque la copia local resuelve uno de los problemas, no los resolverá a todos. Hacerlo seguro para hilos requerirá código adicional alrededor del evento real. No quería dar la impresión de que el código es seguro para subprocesos, por lo tanto, la última frase. – ollb

1

Editar: Como señala el OP, esta respuesta no se dirige al cuerpo de la pregunta. Sin embargo, algunos pueden encontrar útil porque proporciona una respuesta para el título de la pregunta (si se toma por sí mismo):

¿Por qué C# que requieren para escribir un cheque nulo cada vez que se dispare un evento?

También proporciona un contexto para la intención del cuerpo de la pregunta que algunos pueden encontrarle útil. Entonces, por esas razones y this advice en Meta, voy a dejar esta respuesta.


texto original:

En su artículo de MSDN How to: Publish Events that Conform to .NET Framework Guidelines (C# Programming Guide) (Visual Studio 2013), Microsoft incluye el siguiente comentario en su ejemplo:

// Make a temporary copy of the event to avoid possibility of 
// a race condition if the last subscriber unsubscribes 
// immediately after the null check and before the event is raised. 

Aquí es un extracto mayor del código de ejemplo de Microsoft que da contexto a ese comentario.

// Wrap event invocations inside a protected virtual method 
// to allow derived classes to override the event invocation behavior 
protected virtual void OnRaiseCustomEvent(CustomEventArgs e) 
{ 
    // Make a temporary copy of the event to avoid possibility of 
    // a race condition if the last subscriber unsubscribes 
    // immediately after the null check and before the event is raised. 
    EventHandler<CustomEventArgs> handler = RaiseCustomEvent; 

    // Event will be null if there are no subscribers 
    if (handler != null) 
    { 
     // Format the string to send inside the CustomEventArgs parameter 
     e.Message += String.Format(" at {0}", DateTime.Now.ToString()); 

     // Use the() operator to raise the event. 
     handler(this, e); 
    } 
} 
+0

La pregunta no es "¿Por qué es necesario este patrón?"; la pregunta es "¿Por qué C# te hace escribir y no encapsularlo dentro del uso del operador'() 'del evento?" –

0

Tenga en cuenta que a partir de C# 6, el lenguaje ahora proporciona una sintaxis concisa para realizar esta comprobación nula convenientemente. Ej .:

public event EventHandler SomeEvent; 

private void M() 
{ 
    // raise the event: 
    SomeEvent?.Invoke(this, EventArgs.Empty); 
} 

Ver Null Conditional Operator

Cuestiones relacionadas