web-dev-qa-db-fra.com

Pourquoi TEventArgs n'a-t-il pas été rendu contravariant dans le modèle d'événement standard de l'écosystème .NET?

En apprenant plus sur le modèle d'événement standard dans .NET, j'ai constaté qu'avant d'introduire des génériques en C #, la méthode qui gérera un événement est représentée par ce type de délégué:

//
// Summary:
//     Represents the method that will handle an event that has no event data.
//
// Parameters:
//   sender:
//     The source of the event.
//
//   e:
//     An object that contains no event data.
public delegate void EventHandler(object sender, EventArgs e);

Mais après l'introduction des génériques en C # 2, je pense que ce type de délégué a été réécrit en utilisant la généricité:

//
// Summary:
//     Represents the method that will handle an event when the event provides data.
//
// Parameters:
//   sender:
//     The source of the event.
//
//   e:
//     An object that contains the event data.
//
// Type parameters:
//   TEventArgs:
//     The type of the event data generated by the event.
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

J'ai deux questions ici:

Tout d'abord, pourquoi la TEventArgs paramètre de type n'a pas été contravariante ?

Si je ne me trompe pas, il est recommandé de rendre contraires les paramètres de type qui apparaissent en tant que paramètres formels dans la signature d'un délégué et le paramètre de type qui sera le type de retour dans la covariante de signature du délégué.

Dans le livre de Joseph Albahari, C # en bref, je cite:

Si vous définissez un type de délégué générique, il est recommandé de:

  • Marquez un paramètre de type utilisé uniquement sur la valeur de retour comme covariant (out).
  • Marquez tous les paramètres de type utilisés uniquement sur les paramètres comme contravariants (in).

Cela permet aux conversions de fonctionner naturellement en respectant les relations d'héritage entre les types.

Deuxième question: pourquoi n'y avait-il pas de contrainte générique pour faire en sorte que les TEventArgs dérivent de System.EventArgs ?

Comme suit:

public delegate void EventHandler<TEventArgs>  (object source, TEventArgs e) where TEventArgs : EventArgs; 

Merci d'avance.

Modifié pour clarifier la deuxième question:

Il semble que la contrainte générique sur TEventArgs ( où TEventArgs: EventArgs ) était là avant et ait été supprimée par Microsoft, donc apparemment, l'équipe de conception a réalisé qu'elle n'avait pas beaucoup de sens pratique.

J'ai modifié ma réponse pour inclure certaines des captures d'écran de

source de référence .NET

enter image description here

23
Zack ISSOIR

Tout d'abord, pour répondre à certaines préoccupations dans les commentaires à la question: Je repousse généralement les questions "pourquoi pas" parce qu'il est difficile de trouver des raisons concises pour lesquelles tout le monde dans le monde a choisi de ne pas faire ce travail , et parce que tout le travail n'est pas effectué par défaut . Au contraire, vous devez trouver une raison pour faire travailler, et retirer des ressources à d'autres travaux c'est moins important de le faire.

De plus, les questions "pourquoi pas" de ce formulaire, qui posent des questions sur les motivations et les choix des personnes qui travaillent dans une entreprise particulière, ne peuvent être répondues que par les personnes qui ont pris cette décision, qui ne sont probablement pas ici.

Cependant, dans ce cas, nous pouvons faire une exception à ma règle générale de fermer les questions "pourquoi pas" parce que la question illustre un point important sur la covariance des délégués que je n'ai jamais écrit auparavant.

Je n'ai pas pris la décision de garder les délégués à l'événement non-variant, mais si j'avais été en mesure de le faire, j'aurais gardé les délégués à l'événement non-variant, pour deux raisons.

Le premier est purement un point "encourager les bonnes pratiques". Les gestionnaires d'événements sont généralement conçus à cet effet pour gérer un événement particulier, et il n'y a aucune bonne raison que je sache pour rendre plus facile qu'il ne l'est déjà d'utiliser des délégués qui ont des incompatibilités dans la signature en tant que gestionnaires, même si ces incompatibilités peuvent être traités par la variance. Un gestionnaire d'événements qui correspond exactement à tous égards à l'événement qu'il est censé gérer me donne plus de certitude que le développeur sait ce qu'il fait lors de la construction d'un workflow piloté par les événements.

C'est une raison assez faible. La raison la plus forte est également la raison la plus triste.

Comme nous le savons, les types délégués génériques peuvent être rendus covariants dans leurs types de retour et contravariants dans leurs types de paramètres; nous pensons normalement à la variance dans le contexte de la compatibilité des affectations. Autrement dit, si nous avons un Func<Mammal, Mammal> en main, on peut l'assigner à une variable de type Func<Giraffe, Animal> et sachez que la fonction sous-jacente prendra toujours un mammifère - parce que maintenant il n'obtiendra que des girafes - et rendra toujours un animal - parce qu'il retourne des mammifères.

Mais nous savons également que les délégués peuvent être additionnés; les délégués sont immuables, donc ajouter deux délégués ensemble en produit un troisième; la somme est la composition séquentielle des sommets.

Les événements de type champ sont implémentés à l'aide de la sommation des délégués; c'est pourquoi l'ajout d'un gestionnaire à un événement est représenté par +=. (Je ne suis pas un grand fan de cette syntaxe, mais nous sommes coincés avec elle maintenant.)

Bien que ces deux fonctionnalités fonctionnent bien indépendamment l'une de l'autre, elles fonctionnent mal en combinaison. Lorsque j'ai implémenté la variance des délégués, nos tests ont découvert rapidement qu'il y avait un certain nombre de bogues dans le CLR concernant l'ajout de délégués où les types de délégués sous-jacents étaient incompatibles en raison de conversions activées par la variance. Ces bogues existaient depuis CLR 2.0, mais jusqu'à C # 4.0, aucun langage courant n'avait jamais exposé les bogues, aucun cas de test n'avait été écrit pour eux, etc.

Malheureusement, je ne me souviens pas quels étaient les reproducteurs des bogues; c'était il y a douze ans et je ne sais pas si j'ai encore des notes cachées sur un disque quelque part.

Nous avons travaillé avec l'équipe du CLR à l'époque pour essayer de résoudre ces bogues pour la prochaine version du CLR, mais ils n'étaient pas considérés comme une priorité suffisamment élevée par rapport à leur risque. Beaucoup de types comme IEnumerable<T> et IComparable<T> et ainsi de suite ont été rendus variantes dans ces versions, tout comme les types Func et Action, mais il est rare de combiner deux _ Funcs utilisant une variante de conversion . Mais pour les délégués à l'événement, leur seul but dans la vie est d'être additionnés; ils seraient ajoutés tout le temps, et s'ils avaient été des variantes, il y aurait eu un risque d'exposer ces bogues à un grand nombre d'utilisateurs.

J'ai perdu la trace des problèmes peu de temps après C # 4 et honnêtement, je ne sais pas s'ils ont été résolus. Essayez d'ajouter ensemble des délégués incompatibles dans diverses combinaisons et voyez si quelque chose de mauvais se produit!

C'est donc une bonne mais malheureuse raison pour laquelle ne pas faire une variante des délégués d'événements dans le délai de sortie de C # 4.0. S'il y a encore une bonne raison, je ne sais pas. Il faudrait demander à quelqu'un de l'équipe CLR.

39
Eric Lippert