Pourquoi les méthodes magiques ont-elles été implémentées en C #?

16

En C #, j'ai commencé à voir toutes ces méthodes magiques surgir, sans être sauvegardées par une interface. Pourquoi a-t-il été choisi?

Laissez-moi expliquer.

Auparavant en C #, si un objet implémentait l' IEnumerableinterface, il serait automatiquement itérable par une foreachboucle. Cela a du sens pour moi, car il est soutenu par une interface, et si je devais avoir ma propre Iteratorfonction dans la classe en cours d'itération, je pourrais le faire sans craindre que cela signifierait par magie autre chose.

Maintenant, apparemment, (je ne sais pas quand), ces interfaces ne sont plus nécessaires. Il a juste besoin d'avoir les bonnes conversions de noms.

Un autre exemple est de rendre n'importe quel objet attendable en ayant une méthode nommée exactement GetAwaiter qui a quelques propriétés spécifiques.

Pourquoi ne pas créer une interface comme ils l'ont fait avec IEnumerableou INotifyPropertyChangedsauvegarder cette "magie" statiquement?

Plus de détails sur ce que je veux dire ici:

http://blog.nem.ec/2014/01/01/magic-methods-c-sharp/

Quels sont les avantages et les inconvénients des méthodes magiques, et y a-t-il un endroit en ligne où je peux trouver des informations sur les raisons pour lesquelles ces décisions ont été prises?

Mathias Lykkegaard Lorenzen
la source
4
Vous devriez modifier votre opinion personnelle pour savoir pourquoi «la magie est mauvaise» si vous ne voulez pas vous clore.
DougM
2
Si vous voulez savoir pourquoi Anders Hejlsberg a fait cela, vous devrez lui demander. Je peux seulement vous dire pourquoi j'aurais fait cela, et c'est pour la compatibilité ascendante. Les méthodes d'extension vous permettent de "simuler" l'ajout de méthodes aux types existants, mais il n'y a pas d'interface d'extension. Si vous avez besoin d'une interface pour, disons, async/ await, cela ne fonctionnera qu'avec le code qui a été écrit après que .NET 4.5 est devenu suffisamment répandu pour être une cible viable ... ce qui est fondamentalement maintenant. Mais une traduction purement syntaxique en appels de méthode me permet d'ajouter des awaitfonctionnalités aux types existants après coup.
Jörg W Mittag
2
Notez qu'il s'agit essentiellement d'une orientation d'objet appliquée: tant qu'un objet répond aux messages appropriés, il est considéré comme étant du type correct.
Jörg W Mittag
7
Vos "précédemment" et "maintenant" sont en arrière - la méthode magique a été implémentée comme fonctionnalité de base pour une foreachboucle au début. Il n'a jamais été nécessaire que l'objet à implémenter IEnumerablepour foreachfonctionner. C'est juste une convention de le faire.
Jesse C. Slicer
2
@gbulmer c'est là que je me souviens avoir eu l'idée de: blogs.msdn.com/b/ericlippert/archive/2011/06/30/… (et dans une moindre mesure ericlippert.com/2013/07/22/… )
Jesse C. Slicer

Réponses:

16

En général, des «méthodes magiques» sont utilisées lorsqu'il n'est pas possible de créer une interface qui fonctionnerait de la même manière.

Quand il a foreachété introduit pour la première fois en C # 1.0 (ce comportement n'est certainement pas récent), il a dû utiliser des méthodes magiques, car il n'y avait pas de génériques. Les options étaient essentiellement:

  1. Utilisez le non générique IEnumerableet IEnumeratorcela fonctionne avec objects, ce qui signifie des types de valeur de boxe. Étant donné que l'itération de quelque chose comme une liste de ints devrait être très rapide et ne devrait certainement pas créer beaucoup de valeurs de boîte à ordures, ce n'est pas un bon choix.

  2. Attendez les génériques. Cela signifierait probablement retarder .Net 1.0 (ou au moins foreach) de plus de 3 ans.

  3. Utilisez des méthodes magiques.

Donc, ils ont choisi l'option # 3 et elle est restée avec nous pour des raisons de compatibilité descendante, même si depuis .Net 2.0, l'exigence IEnumerable<T>aurait également fonctionné.


Les initialiseurs de collection peuvent avoir un aspect différent sur chaque type de collection. Comparez List<T>:

public void Add(T item)

et Dictionary<TKey, TValue>:

public void Add(TKey key, TValue value)

Vous ne pouvez pas avoir une seule interface qui prendrait en charge uniquement le premier formulaire pour List<T>et uniquement le second pour Dictionary<TKey, TValue>.


Les méthodes LINQ sont généralement implémentées en tant que méthodes d'extension (de sorte qu'il ne peut y avoir qu'une seule implémentation de LINQ to Object pour tous les types qui l'implémentent IEnumerable<T>), ce qui signifie qu'il n'est pas possible d'utiliser une interface.


Pour await, la GetResult()méthode peut renvoyer voidun type ou un type T. Encore une fois, vous ne pouvez pas avoir une seule interface capable de gérer les deux. Bien qu'il awaitsoit partiellement basé sur l'interface: l'attente doit implémenter INotifyCompletionet peut également implémenter ICriticalNotifyCompletion.

svick
la source
1
Êtes-vous certain "qu'ils ont choisi l'option # 3" pour C # 1.0? Si je me souviens bien, c'était l'option 1. Ma mémoire est que C # a revendiqué une supériorité significative sur Java parce que le compilateur C # a fait la «boxing automatique» et la «décompression automatique». On a prétendu, C # fait code comme ce travail foreach beaucoup mieux que Java en partie à cause de cela.
gbulmer
1
La documentation de foreachC # 1.2 (il n'y a rien sur MSDN pour C # 1.0) dit que l'expression dans foreach"Evalue à un type qui implémente IEnumerableou à un type qui déclare une GetEnumeratorméthode". Et je ne suis pas sûr de ce que vous entendez par là, déballer en C # est toujours explicite.
svick
1
@gbulmer Et la spécification ECMA pour C # de décembre 2001 (ce qui signifie que c'est pour C # 1.0) dit également que (§15.8.4): «Un type C est dit être un type de collection s'il implémente l'interface System.IEnumerable ou met en œuvre le modèle de collecte en répondant à tous les critères suivants […] ».
svick
1
@svick foreach(T x in sequence)applique des transtypages explicites aux Téléments de la séquence. Par conséquent, si la séquence est un simple IEnumerableet Test un type de valeur, elle sera décompressée sans qu'une conversion explicite ne soit écrite dans votre code. L'une des parties les plus laides de C #.
CodesInChaos
@CodesInChaos "Auto-unboxing" semble assez général, je ne savais pas qu'il faisait référence à cette fonctionnalité spécifique. (Je suppose que j'aurais dû, puisque nous en parlions foreach.)
svick