Mi rendo conto che questa domanda ha più di 10 anni, ma mi sembra che non solo la risposta più ovvia non sia stata affrontata, ma che forse non è davvero chiaro dalla domanda una buona comprensione di ciò che accade sotto le coperte. Inoltre, ci sono altre domande sull'associazione tardiva e sul significato dei delegati e degli lambda (ne parleremo più avanti).
Prima di affrontare l'elefante / gorilla da 800 libbre nella stanza, quando scegliere eventvs Action<T>/ Func<T>:
- Utilizzare un lambda per eseguire un'istruzione o un metodo. Utilizzare
eventquando si desidera più di un pub / modello secondario con più istruzioni / lambdas / funzioni che verranno eseguite (questa è una grande
differenza fin dall'inizio).
- Utilizzare un lambda quando si desidera compilare istruzioni / funzioni in alberi di espressioni. Utilizzare i delegati / eventi quando si desidera partecipare a più tardi l'associazione tardiva tradizionale come quella utilizzata nella riflessione e l'interoperabilità COM.
Come esempio di un evento, consente di collegare un set di eventi semplice e "standard" utilizzando una piccola applicazione console come segue:
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}");
}
}
L'output avrà il seguente aspetto:

Se hai fatto lo stesso con Action<int> o Action<object, SomeStandardArgs>, vedresti solo SomeSimpleEvent2e SomeStandardEvent2.
Cosa sta succedendo dentro event?
Se ci espandiamo FireNiceEvent , il compilatore sta effettivamente generando quanto segue (ho omesso alcuni dettagli riguardo alla sincronizzazione dei thread che non sono rilevanti per questa discussione):
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)
}
}
Il compilatore genera una variabile delegata privata che non è visibile nello spazio dei nomi della classe in cui viene generata. Quel delegato è ciò che viene utilizzato per la gestione delle iscrizioni e la partecipazione vincolante in ritardo, e l'interfaccia pubblica è familiare+= e-= operatori che tutti abbiamo imparato a conoscere e ad amare:)
È possibile personalizzare il codice per i gestori Aggiungi / Rimuovi modificando l'ambito di FireNiceEvent delegato in protetto. Ciò ora consente agli sviluppatori di aggiungere hook personalizzati agli hook, come la registrazione o gli hook di sicurezza. Questo rende davvero alcune funzionalità molto potenti che ora consentono un'accessibilità personalizzata alla sottoscrizione in base ai ruoli degli utenti, ecc. Puoi farlo con lambdas? (In realtà puoi compilare alberi delle espressioni personalizzati, ma questo va oltre lo scopo di questa risposta).
Per rispondere a un paio di punti di alcune delle risposte qui:
Non c'è davvero alcuna differenza nella "fragilità" tra la modifica dell'elenco args Action<T>e la modifica delle proprietà in una classe derivata EventArgs. Entrambi non solo richiedono una modifica della compilazione, ma cambieranno anche un'interfaccia pubblica e richiederanno il controllo delle versioni. Nessuna differenza.
Per quanto riguarda lo standard industriale, dipende da dove viene utilizzato e perché. Action<T>e tale viene spesso utilizzato in IoC e DI, e eventviene spesso utilizzato nel routing dei messaggi come i framework di tipo GUI e MQ. Si noti che ho detto spesso , non sempre .
I delegati hanno vite diverse rispetto a lambda. Bisogna anche essere consapevoli della cattura ... non solo con la chiusura, ma anche con la nozione di "guarda cosa trascinava il gatto". Ciò influisce sul footprint / durata della memoria e sulle perdite di gestione.
Ancora una cosa, qualcosa a cui ho fatto riferimento in precedenza ... la nozione di associazione tardiva. Lo vedrai spesso quando usi framework come LINQ, riguardo a quando un lambda diventa 'live'. Questo è molto diverso dall'associazione tardiva di un delegato, che può avvenire più di una volta (cioè la lambda è sempre lì, ma l'associazione si verifica su richiesta tutte le volte che è necessario), al contrario di una lambda, che una volta che si verifica, viene eseguita - la magia è sparita e il metodo (i) / proprietà (e) si legheranno sempre. Qualcosa da tenere a mente.