Percebo que essa pergunta tem mais de 10 anos, mas parece-me que não apenas a resposta mais óbvia não foi abordada, mas que talvez não esteja realmente claro da pergunta uma boa compreensão do que acontece nos bastidores. Além disso, há outras perguntas sobre o vínculo tardio e o que isso significa em relação aos delegados e lambdas (mais sobre isso mais tarde).
Primeiro a abordar o elefante / gorila de 300 kg na sala, quando escolher eventvs Action<T>/ Func<T>:
- Use um lambda para executar uma instrução ou método. Use
eventquando desejar mais de um modelo pub / sub com várias instruções / lambdas / funções que serão executadas (esta é uma das principais
diferença logo de cara).
- Use um lambda quando desejar compilar instruções / funções para árvores de expressão. Use delegados / eventos quando quiser participar de ligações tardias mais tradicionais, como as usadas na reflexão e na interoperabilidade COM.
Como exemplo de um evento, vamos conectar um conjunto de eventos simples e "padrão" usando um pequeno aplicativo de console da seguinte maneira:
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}");
}
}
A saída terá a seguinte aparência:

Se você fizesse o mesmo com Action<int>ou Action<object, SomeStandardArgs>, você apenas veria SomeSimpleEvent2eSomeStandardEvent2 .
Então, o que está acontecendo dentro de event ?
Se expandirmos FireNiceEvent, o compilador está realmente gerando o seguinte (omiti alguns detalhes em relação à sincronização de threads que não são relevantes para esta discussão):
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)
}
}
O compilador gera uma variável delegada privada que não é visível para o espaço para nome da classe em que é gerada. Esse delegado é o que é usado para gerenciamento de assinaturas e participação tardia de ligações, e a interface voltada ao público é a familiar +=e-= operadores que todos conhecemos e amamos:)
Você pode personalizar o código para os manipuladores de adicionar / remover, alterando o escopo do FireNiceEvent delegado para protegido. Agora, os desenvolvedores podem adicionar ganchos personalizados aos ganchos, como ganchos de log ou segurança. Isso realmente cria alguns recursos muito poderosos que agora permitem acessibilidade personalizada à assinatura com base nas funções do usuário etc. Você pode fazer isso com lambdas? (Na verdade, você pode compilar árvores de expressão customizadas, mas isso está além do escopo desta resposta).
Para abordar alguns pontos de algumas das respostas aqui:
Realmente não há diferença na 'fragilidade' entre alterar a lista de argumentos Action<T>e alterar as propriedades em uma classe derivada EventArgs. Eles não apenas exigirão uma alteração de compilação, como também mudarão uma interface pública e exigirão controle de versão. Não faz diferença.
Com relação a qual é um padrão do setor, isso depende de onde isso está sendo usado e por quê. Action<T>e tal é frequentemente usado em IoC e DI, e eventé freqüentemente usado em roteamento de mensagens, como estruturas de tipo GUI e MQ. Note que eu disse muitas vezes , nem sempre .
Os delegados têm vidas diferentes das lambdas. Também é preciso estar ciente da captura ... não apenas com o fechamento, mas também com a noção de 'veja o que o gato arrastou'. Isso afeta a pegada / vida útil da memória e também o gerenciamento de vazamentos.
Mais uma coisa, algo que eu referenciei anteriormente ... a noção de ligação tardia. Você verá isso frequentemente ao usar uma estrutura como LINQ, sobre quando um lambda se torna 'ativo'. Isso é muito diferente da ligação tardia de um delegado, que pode acontecer mais de uma vez (ou seja, o lambda está sempre lá, mas a ligação ocorre sob demanda sempre que necessário), em oposição a um lambda, que, uma vez que ocorre, está pronto - a mágica se foi, e o (s) método (s) / propriedade (s) sempre será vinculado. Algo a ter em mente.