Delegado multidifusión de comportamiento extraño en C #?

Tengo este simple evento:

public class ClassA { public event Func Ev; public int Do(string l) { return Ev(l); } } 

Y 2 Métodos:

  static int Display(string k) { return k.Length; } static int Display_2(string k) { return k.Length*10; } 

Estoy registrando este evento:

  ClassA a = new ClassA(); a.Ev += Display; a.Ev += Display_2; 

Ahora, estoy ejecutando:

  Console.WriteLine(a.Do("aaa")); 

La salida :

introduzca la descripción de la imagen aquí

Qué ???

  • Él tiene en la lista de invocación 2 métodos! los ejecutó, pero ¿por qué muestra solo el resultado del último registro?

  • ¿A dónde se ha ido el resultado de "3" ? (la primera invocación)? ( aunque se ejecutó tanto display + display_2 … No esperaba que console.write repitiera los resultados, pero tampoco esperaba que él decidiera cuál mostrar ) .

editar:

introduzca la descripción de la imagen aquí

Hay tres aspectos en juego aquí:

  1. La implementación del evento.
  2. El comportamiento de la combinación de delegates.
  3. El comportamiento de invocar a un delegado cuya lista de invocación tiene varias entradas

Para el punto 1, tienes un evento similar a un campo . La sección 10.8.1 de la especificación C # 4 da un ejemplo e indica que:

Fuera de la statement de la clase Button , el miembro Click se puede usar solo en la parte izquierda de los operadores += y -= , como en

 b.Click += new EventHandler(...); 

que agrega un delegado a la lista de invocación del evento Click

(énfasis mío). La especificación también deja en claro que un evento similar a un campo crea un campo delegado, que se usa desde dentro de la clase para la invocación.

Más en general (punto 2), la sección 7.8.4 de la especificación C # 4 se refiere a la combinación de delegates a través de + y += :

Combinación de delegates. Cada tipo de delegado proporciona implícitamente el siguiente operador predefinido, donde D es el tipo de delegado:

 D operator +(D x, D y) 

El binario + operato realiza una combinación de delegates cuando ambos operandos son de algún tipo de delegado D [… omita los bits donde x o y son nulos …] De lo contrario, el resultado de la operación es un nuevo delegado que, cuando se invoca, invoca al primer operando y luego invoca al segundo operando .

(De nuevo, énfasis mío.)

Finalmente, punto 3 – invocación de eventos y valores de retorno. La sección 15.4 de la especificación C # establece:

Si la invocación del delegado incluye parámetros de salida o un valor de retorno, su valor final provendrá de la invocación del último delegado en la lista.

Más generalmente, depende de la implementación del evento. Si utiliza una implementación de evento que utiliza los pasos “normales” de combinación / eliminación de delegates, todo está garantizado. Si empiezas a escribir una implementación personalizada que hace locuras, es un asunto diferente.

Al invocar a un delegado no nulo de multidifusión se devuelve el valor del último controlador que se ejecutó.

Tienes muy poco control sobre quién es el primero o el último. Es un sistema pésimo de usar.

Es por eso que la mayoría de los eventos y delegates vuelven void .

Como regla general, no tiene sentido que los eventos devuelvan un valor.

Si desea obtener información de un controlador de eventos, tiene más sentido que los controladores de eventos muten un parámetro de entrada, o simplemente llamen a otro método de cualquier objeto que haya activado el evento para comunicar otra cosa.

Normalmente esto ni siquiera aparece porque los eventos lógicamente están pasando información a los controladores de eventos y no tienen ninguna necesidad de obtener información de los controladores de eventos. Sinceramente, es un signo de olor de código. A un evento no debería importarle si alguien se ha suscrito a él, quiénes podrían ser, qué podrían estar haciendo o incluso si hay suscriptores. Confiar en un valor de retorno de ellos, entonces simplemente crea un acoplamiento demasiado apretado.

Los eventos se repiten en el orden en que se adjuntaron. Debido a que está utilizando un valor de retorno, el valor que obtiene es el último invocado.

Sin embargo, este no es realmente un patrón normal para los eventos. Probablemente quieras algo más como:

 public class MyEventArgs : EventArgs { public MyEventArgs() { Results = new List(); } public string InputString{get;set;} public List Results{get;set;} } public event EventHandler Ev public int Do(string l) { MyEventArgs e = new MyEventArgs(); e.InputString = l; if(Ev != null) Ev(this, e); return e.Results.Sum(); } 

y entonces

 static int Display(object sender, MyEventArgs e) { return e.Results.Add(k.Length); } static int Display_2(object sender, MyEventArgs e) { return e.Results.Add(k.Length*10); }