2010-10-19 11 views
5

Al llamar a un delegado, siempre debe verificar si no es nulo. Esto es a menudo causa de errores. Dado que los delegados son más o menos solo una lista de funciones, supongo que esto podría haber sido fácilmente verificado por el delegado.¿Por qué el delegado no maneja el evento nulo?

¿Alguien sabe por qué se ha implementado tal cual?

+6

Si un delegado es nulo, ¿cómo lo usaría para comprobarlo? Es como llamar a la puerta de la habitación de su hijo y preguntarle si está allí, y esperar un "No" de él si no lo hace. – BoltClock

+0

No es solo una lista de funciones, sino más bien una lista de llamadas a métodos diferibles (incluidos los parámetros y el objeto al que está asociado el método). –

+0

Vea [esta pregunta] (http://stackoverflow.com/questions/170907/is-there-a-downside-to-adding-an-anonymous-empty-delegate-on-event-declaration) para una forma de hacer las comprobaciones nulas son innecesarias (a costa de ciclos de CPU adicionales). –

Respuesta

1

Supongo que las razones son la historia y la coherencia.

Es consistente con la forma en que se manejan otros tipos; p.ej.no hay asistencia de idioma o plataforma al tratar con null s - es responsabilidad de los programadores asegurarse de que el marcador de posición nulo nunca se use realmente - no hay forma de llamar solo opcionalmente a un método si la referencia de objeto que desea invocar tampoco es nula , después de todo.

Es historia, ya que las referencias nulas se incluyen por defecto en la mayoría de los tipos, aunque esto no es necesario. Es decir, en lugar de tratar los tipos de referencia como los tipos de valor y requieren una anotación adicional de "anulabilidad" para permitir la anulación, los tipos de referencia son siempre nullable. Estoy seguro de que hubo razones para esto en el pasado cuando se diseñaron Java y .NET, pero presenta muchos errores innecesarios y complejidades que son fáciles de evitar en un lenguaje fuertemente tipado (por ejemplo, tipos de valores .NET). Pero dada la inclusión histórica de null como un valor "inválido" de todo el sistema de tipos, por así decirlo, es natural que lo haga también para los delegados.

1

Si una instancia de delegado es null, ¿cómo puede "verificarse a sí mismo"? Es null ... eso significa que no existe, básicamente. No hay nada allí para controlarse en primer lugar. Es por eso que su código que intenta llamar al delegado debe hacer esa comprobación, primero.

+0

Hm, no sé el interno del delegado. En realidad solo haces un – martin

+0

El 'interno' del delegado * no importa en absoluto *. Un objeto inexistente no puede hacer nada, punto. –

+0

El interno de los delegados no importa. Lo que importa es la especificación del lenguaje ante tal situación. Y, por extensión, si desea que varios lenguajes .NET aborden la misma situación de forma idéntica (lo cual no es necesario), lo que la especificación IL dice que debería suceder. No hay ningún requisito para lanzar una excepción; la especificación * elige * hacerlo por * razón *. –

4

El delegado no puede verificarlo. Como es nulo, no puede invocar métodos en él.

El compilador C# por otro lado podría insertar automáticamente la verificación nula. Pero no sé qué comportamiento usar si el delegado es una función con un valor de retorno.

Se podría argumentar que el compilador de C# debe insertar esa verificación para las funciones nulas para evitar el código repetitivo en ese caso común. Pero esa decisión ahora está en el pasado, solo puedes arreglarla sin romper los programas existentes si obtienes una máquina del tiempo.

Puede utilizar los métodos de extensión (ya que pueden ser llamados en un objeto nulo) para automatizar el nulo comprobar si hay eventos:
http://blogs.microsoft.co.il/blogs/shayf/archive/2009/05/25/a-handy-extension-method-raise-events-safely.aspx
Y puesto que un parámetro es una variable temporal que no es necesario asignar manualmente el evento a una variable temporal para seguridad de hilos.

+0

Bueno, ¿qué comportamiento usarías para un delegado de multidifusión con un valor de retorno? –

+0

... el punto es que los delegados son ambas * funciones * donde la multidifusión y la nulabilidad simplemente no tienen sentido, y los eventos, donde ambos pueden ser perfectamente normales y no hay razón para tirar. –

+0

AFAIK .net simplemente devuelve el valor del último delegado. Personalmente, no estoy seguro de si hubiera permitido a los delegados de multidifusión de funciones no nulas. – CodesInChaos

2

La razón es el rendimiento (de hecho, esa es mi mejor estimación). Los eventos y los delegados están hechos con mucha magia de compilación, cosas que no pueden ser replicadas por el simple código C#. Pero debajo hay algo como esto:

Un delegado es una clase generada por el compilador que hereda de la clase MulticastDelegate, que a su vez deriva de la clase Delegate. Tenga en cuenta que estas dos clases son mágicas y usted no puede heredarlas usted mismo (bueno, tal vez sí, pero no podrá usarlas muy bien).

Un evento sin embargo se implementa algo como esto:

private MyEventDelegateClass __cgbe_MyEvent; // Compiler generated backing field 

public event MyEventDelegateClass MyEvent 
{ 
    add 
    { 
     this.__cgbe_MyEvent = (MyEventDelegateClass)Delegate.Combine(this.__cgbe_MyEvent, value); 
    } 
    remove 
    { 
     this.__cgbe_MyEvent = (MyEventDelegateClass)Delegate.Remove(this.__cgbe_MyEvent, value); 
    } 
    get 
    { 
     return this.__cgbe_MyEvent; 
    } 
} 

bien, así que esto no es verdadero código (ni es exactamente la forma en que es en la vida real), pero debe darle una idea de lo que está pasando

El punto es que el campo de respaldo inicialmente es realmente nulo. De esta forma, no hay gastos generales para crear una instancia de MyEventDelegateClass al crear su objeto. Y es por eso que debes verificar null antes de invocar. Más adelante, cuando los manejadores se agregan/eliminan, se crea una instancia de MyEventDelegateClass y se asigna al campo. Y cuando se elimina el último controlador, esta instancia también se pierde y el campo de respaldo se restablece a nulo nuevamente.

Este es el principio de "usted no paga por lo que no usa". Mientras no uses el evento, no habrá gastos generales para él. Sin memoria extra, sin ciclos de CPU adicionales.

+0

Correcto, pero para un típico evento de retorno de vacío, un campo de respaldo nulo representa la lista vacía de manejadores de eventos, y llamarlo es semánticamente no operativo. Por lo tanto, es razonable usar un valor de centinela (eficiente) para la lista vacía, pero sin embargo no lanzar al llamarlo. –

+0

@Eamon Nerbonne - Solo puedo adivinar lo que pensó el equipo de .NET cuando construyeron esto. Mi primer pensamiento es que no querían hacer la distinción entre un evento de devolución de vacío y un evento de devolución de vacío. De esta forma, el compilador es más simple y el código es más uniforme (y el programador necesita saber menos). –

6

Esto puede estar indicando lo obvio, pero puede declarar su evento para apuntar a un controlador no operativo y luego no necesita comprobar nulo al invocar.

public event EventHandler MyEvent = delegate { }; 

A continuación, puede llamar a los manejadores apuntado por MyEvent sin comprobar nula.

+0

No me gusta hacer eso porque algún otro código puede volver a establecer el evento como nulo. Prefiero anular la verificación. –

+1

@Iain: el código debería estar en el tipo de declaración, ya que el evento no se puede establecer como nulo desde el exterior, por lo que limita el potencial de errores aquí, pero tiene razón en que podría suceder. –

+0

Aye. La probabilidad de que realmente suceda en la práctica es bastante pequeña. –

1

"Dado que los delegados son más o menos justo una lista de funciones, yo asumiría, que esto podría haber sido fácilmente revisado por el propio delegado".

Su suposición es incorrecta. Simple y simple.

3

Internamente, el compilador generará 2 métodos add_MyEvent y remove_MyEvent para cada evento declarado en una clase. Cuando escribe, MyEvent + = ..., el compilador generará una llamada a add_MyEvent que a su vez llamará a System.Delegate.Combine.

Combinar toma 2 delegados en el parámetro y crea un nuevo delegado (multidifusión) de los 2 delegados originales, manejando el caso cuando uno de ellos es nulo (que es el caso la primera vez que llama a + =).

Supongo que el compilador podría haber sido un poco más inteligente y también manejar la llamada de evento para que cuando llame al MyEvent(), realmente generaría una verificación nula e invocaría al delegado solo si no fuera nulo. (Sería bueno tener la opinión de Eric Lippert sobre eso).

+0

Siempre prefiero que esos cheques sean de mi propiedad, personalmente. Demasiadas 'pequeñas verificaciones' nos están haciendo el marco, y estamos de vuelta en el viejo mundo VB6. +1 para la explicación detallada de los delegados de multidifusión que se están creando. –

+0

Entiendo tu punto de vista de Andrew, pero ¿puedes pensar en algún caso en el que no quieras verificar la nulidad de un evento? –

+0

Absolutamente; en un caso en el que estoy tan seguro de que se ha asignado una instancia de delegado a la variable, es mejor simplemente atrapar la 'NullReferenceException' que sé que no va a suceder de todos modos. Por ejemplo, si todo el código involucrado es autónomo. –

Cuestiones relacionadas