Me doy cuenta de que esta pregunta tiene más de 10 años, pero me parece que no solo no se ha abordado la respuesta más obvia, sino que tal vez no esté realmente claro de la pregunta una buena comprensión de lo que sucede debajo de las cubiertas. Además, hay otras preguntas sobre la vinculación tardía y lo que eso significa con respecto a los delegados y lambdas (más sobre eso más adelante).
Primero en abordar el elefante / gorila de 800 lb en la habitación, cuándo elegir eventvs Action<T>/ Func<T>:
- Use una lambda para ejecutar una declaración o método. Úselo
eventcuando desee más de un modelo pub / sub con múltiples declaraciones / lambdas / funciones que se ejecutarán (esta es una gran
diferencia desde el principio).
- Use una lambda cuando desee compilar declaraciones / funciones para árboles de expresión. Utilice delegados / eventos cuando desee participar en un enlace tardío más tradicional, como el utilizado en la reflexión y la interoperabilidad COM.
Como ejemplo de un evento, conectemos un conjunto de eventos simple y 'estándar' usando una pequeña aplicación de consola de la siguiente manera:
public delegate void FireEvent(int num);
public delegate void FireNiceEvent(object sender, SomeStandardArgs args);
public class SomeStandardArgs : EventArgs
{
public SomeStandardArgs(string id)
{
ID = id;
}
public string ID { get; set; }
}
class Program
{
public static event FireEvent OnFireEvent;
public static event FireNiceEvent OnFireNiceEvent;
static void Main(string[] args)
{
OnFireEvent += SomeSimpleEvent1;
OnFireEvent += SomeSimpleEvent2;
OnFireNiceEvent += SomeStandardEvent1;
OnFireNiceEvent += SomeStandardEvent2;
Console.WriteLine("Firing events.....");
OnFireEvent?.Invoke(3);
OnFireNiceEvent?.Invoke(null, new SomeStandardArgs("Fred"));
//Console.WriteLine($"{HeightSensorTypes.Keyence_IL030}:{(int)HeightSensorTypes.Keyence_IL030}");
Console.ReadLine();
}
private static void SomeSimpleEvent1(int num)
{
Console.WriteLine($"{nameof(SomeSimpleEvent1)}:{num}");
}
private static void SomeSimpleEvent2(int num)
{
Console.WriteLine($"{nameof(SomeSimpleEvent2)}:{num}");
}
private static void SomeStandardEvent1(object sender, SomeStandardArgs args)
{
Console.WriteLine($"{nameof(SomeStandardEvent1)}:{args.ID}");
}
private static void SomeStandardEvent2(object sender, SomeStandardArgs args)
{
Console.WriteLine($"{nameof(SomeStandardEvent2)}:{args.ID}");
}
}
La salida se verá de la siguiente manera:

Si hicieras lo mismo con Action<int>o Action<object, SomeStandardArgs>, solo verías SomeSimpleEvent2y SomeStandardEvent2.
Entonces, ¿qué está pasando dentro event?
Si nos expandimos FireNiceEvent, el compilador en realidad está generando lo siguiente (he omitido algunos detalles con respecto a la sincronización de subprocesos que no son relevantes para esta discusión):
private EventHandler<SomeStandardArgs> _OnFireNiceEvent;
public void add_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
{
Delegate.Combine(_OnFireNiceEvent, handler);
}
public void remove_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
{
Delegate.Remove(_OnFireNiceEvent, handler);
}
public event EventHandler<SomeStandardArgs> OnFireNiceEvent
{
add
{
add_OnFireNiceEvent(value)
}
remove
{
remove_OnFireNiceEvent(value)
}
}
El compilador genera una variable delegada privada que no es visible para el espacio de nombres de clase en el que se genera. Ese delegado es lo que se usa para la gestión de suscripciones y la participación vinculante tardía, y la interfaz pública es familiar +=y-= operadores que todos conocemos y amamos:)
Puede personalizar el código para los controladores de agregar / quitar cambiando el alcance de FireNiceEvent delegado a protegido. Esto ahora permite a los desarrolladores agregar ganchos personalizados a los ganchos, como el registro o los ganchos de seguridad. Esto realmente ofrece algunas funciones muy potentes que ahora permiten una accesibilidad personalizada a la suscripción en función de los roles de los usuarios, etc. ¿Se puede hacer eso con lambdas? (En realidad, puede compilando árboles de expresión personalizados, pero eso está más allá del alcance de esta respuesta).
Para abordar algunos puntos de algunas de las respuestas aquí:
Realmente no hay diferencia en la 'fragilidad' entre cambiar la lista de argumentos Action<T>y cambiar las propiedades en una clase derivada de EventArgs. Cualquiera de los dos no solo requerirá un cambio de compilación, ambos cambiarán una interfaz pública y requerirán versiones. Ninguna diferencia.
Con respecto a cuál es un estándar de la industria, eso depende de dónde se esté utilizando y por qué. Action<T>y esto a menudo se usa en IoC y DI, y a eventmenudo se usa en el enrutamiento de mensajes, como los marcos de tipo GUI y MQ. Tenga en cuenta que dije a menudo , no siempre .
Los delegados tienen vidas diferentes a las lambdas. También hay que tener en cuenta la captura ... no solo con el cierre, sino también con la noción de "mira lo que el gato arrastró". Esto afecta la huella / vida útil de la memoria, así como la gestión, también conocida como fugas.
Una cosa más, algo que mencioné anteriormente ... la noción de enlace tardío. A menudo verá esto cuando use un marco como LINQ, en cuanto a cuándo una lambda se vuelve 'en vivo'. Eso es muy diferente a la vinculación tardía de un delegado, lo que puede suceder más de una vez (es decir, el lambda siempre está allí, pero la vinculación ocurre a pedido tantas veces como sea necesario), a diferencia de una lambda, que una vez que ocurre, se hace - la magia se ha ido, y los métodos / propiedad (s) siempre se unirán. Algo para tener en cuenta.