Je me rends compte que cette question a plus de 10 ans, mais il me semble que non seulement la réponse la plus évidente n’a pas été abordée, mais que la question n’a peut-être pas vraiment compris clairement ce qui se passe sous les couvertures. En outre, il y a d'autres questions sur la liaison tardive et ce que cela signifie en ce qui concerne les délégués et les lambdas (nous en parlerons plus tard).
Commencez par vous adresser à l'éléphant / gorille de 800 lb dans la pièce, quand choisir event
vs Action<T>
/ Func<T>
:
- Utilisez un lambda pour exécuter une instruction ou une méthode. À utiliser
event
lorsque vous voulez plus d'un modèle pub / sous avec plusieurs instructions / lambdas / fonctions qui s'exécuteront (c'est une
différence majeure dès le départ).
- Utilisez un lambda lorsque vous souhaitez compiler des instructions / fonctions en arborescences d'expression. Utilisez des délégués / événements lorsque vous souhaitez participer à une liaison tardive plus traditionnelle, telle que celle utilisée dans la réflexion et l'interopérabilité COM.
À titre d'exemple d'événement, connectons un ensemble d'événements simple et `` standard '' à l'aide d'une petite application console comme suit:
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 sortie ressemblera à ceci:

Si vous faisiez la même chose avec Action<int>
ou Action<object, SomeStandardArgs>
, vous ne verriez que SomeSimpleEvent2
et SomeStandardEvent2
.
Alors qu'est-ce qui se passe à l'intérieur event
?
Si nous développons FireNiceEvent
, le compilateur génère en fait ce qui suit (j'ai omis certains détails concernant la synchronisation des threads qui ne sont pas pertinents pour cette discussion):
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)
}
}
Le compilateur génère une variable de délégué privé qui n'est pas visible par l'espace de noms de classe dans lequel elle est générée. Ce délégué est ce qui est utilisé pour la gestion des abonnements et la participation à la liaison tardive, et l'interface publique est les opérateurs familiers +=
et que -=
nous avons tous appris à connaître et à aimer :)
Vous pouvez personnaliser le code des gestionnaires d'ajout / suppression en modifiant la portée du FireNiceEvent
délégué sur protected. Cela permet désormais aux développeurs d'ajouter des hooks personnalisés aux hooks, tels que la journalisation ou des hooks de sécurité. Cela donne vraiment des fonctionnalités très puissantes qui permettent désormais une accessibilité personnalisée à l'abonnement en fonction des rôles des utilisateurs, etc. Pouvez-vous faire cela avec des lambdas? (En fait, vous pouvez en compiler des arborescences d'expressions personnalisées, mais cela dépasse le cadre de cette réponse).
Pour aborder quelques points à partir de certaines des réponses ici:
Il n'y a vraiment aucune différence dans la «fragilité» entre la modification de la liste d'arguments dans Action<T>
et la modification des propriétés d'une classe dérivée de EventArgs
. Les deux nécessiteront non seulement un changement de compilation, mais ils changeront tous les deux une interface publique et nécessiteront un contrôle de version. Aucune différence.
En ce qui concerne la norme de l'industrie, cela dépend de l'endroit où elle est utilisée et pourquoi. Action<T>
et tel est souvent utilisé dans IoC et DI, et event
est souvent utilisé dans le routage de messages tels que les frameworks de type GUI et MQ. Notez que je l'ai dit souvent , pas toujours .
Les délégués ont des durées de vie différentes de celles des lambdas. Il faut aussi être conscient de la capture ... non seulement avec la fermeture, mais aussi avec la notion de «regardez ce que le chat a entraîné». Cela affecte l'empreinte mémoire / la durée de vie ainsi que la gestion des fuites.
Une dernière chose, quelque chose que j'ai mentionné plus tôt ... la notion de liaison tardive. Vous verrez souvent cela lors de l'utilisation d'un framework comme LINQ, concernant le moment où un lambda devient `` live ''. C'est très différent de la liaison tardive d'un délégué, qui peut se produire plus d'une fois (c'est-à-dire que le lambda est toujours là, mais la liaison se produit à la demande aussi souvent que nécessaire), par opposition à un lambda, qui une fois qu'il se produit, c'est fait - la magie a disparu et la ou les méthodes / propriété (s) seront toujours liées. Quelque chose à garder à l'esprit.