Warum wurden magische Methoden in C # implementiert?

16

In C # tauchten all diese magischen Methoden auf, ohne von einer Schnittstelle gesichert zu werden. Warum wurde das gewählt?

Lassen Sie mich erklären.

Wenn in C # zuvor ein Objekt die IEnumerableSchnittstelle implementiert hatte , konnte es automatisch von einer foreachSchleife durchlaufen werden . Das macht für mich Sinn, da es durch eine Schnittstelle gesichert ist und wenn ich meine eigene IteratorFunktion innerhalb der Klasse hätte, die durchlaufen wird, könnte ich das tun, ohne mir Sorgen zu machen, dass es auf magische Weise etwas anderes bedeuten würde.

Nun, anscheinend (nicht sicher wann), werden diese Schnittstellen nicht mehr benötigt. Es müssen nur die richtigen Namenskonvertierungen vorhanden sein.

Ein weiteres Beispiel besteht darin, ein Objekt durch eine Methode mit einem genauen Namen zu warten , GetAwaiter die einige bestimmte Eigenschaften aufweist.

Warum nicht eine Schnittstelle erstellen, wie sie es mit dieser "Magie" gemacht hat IEnumerableoder INotifyPropertyChangedum sie statisch zu sichern?

Weitere Details zu dem, was ich hier meine:

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

Was sind die Vor- und Nachteile magischer Methoden? Gibt es irgendwo im Internet etwas darüber, warum diese Entscheidungen getroffen wurden?

Mathias Lykkegaard Lorenzen
quelle
4
Sie sollten sich Ihre persönliche Meinung überlegen, warum "Magie schlecht ist", wenn Sie nicht geschlossen werden möchten.
DougM,
2
Wenn Sie wissen wollen, warum Anders Hejlsberg das getan hat, müssen Sie ihn fragen. Ich kann Ihnen nur sagen, warum ich das getan hätte, und dies dient der Aufwärtskompatibilität. Mithilfe von Erweiterungsmethoden können Sie bestehende Typen fälschen und Methoden hinzufügen, es gibt jedoch keine Erweiterungsschnittstellen. Wenn Sie eine Schnittstelle für beispielsweise async/ benötigen, awaitfunktioniert dies nur mit Code, der geschrieben wurde, nachdem .NET 4.5 weit genug verbreitet wurde, um ein brauchbares Ziel zu sein. Dies ist im Grunde genommen jetzt der Fall. Aber eine rein syntaktische Übersetzung in Methodenaufrufe ermöglicht es mir await, vorhandene Typen nachträglich zu erweitern.
Jörg W Mittag
2
Beachten Sie, dass dies im Grunde genommen objektorientiert ist: Solange ein Objekt auf die entsprechenden Nachrichten antwortet, wird es als vom richtigen Typ angesehen.
Jörg W Mittag
7
Ihr "vorher" und "jetzt" sind rückwärts - die magische Methode wurde foreachanfangs als Basisfunktionalität für eine Schleife implementiert . Es nie wurde für das Objekt eine Anforderung zur Umsetzung IEnumerablefür foreachzu arbeiten. Es war nur eine Konvention, dies zu tun.
Jesse C. Slicer
2
@ gbulmer an dieser Stelle erinnere ich mich an die Idee von: blogs.msdn.com/b/ericlippert/archive/2011/06/30/… (und in geringerem Maße ericlippert.com/2013/07/22/… )
Jesse C. Slicer

Antworten:

16

Im Allgemeinen werden "magische Methoden" verwendet, wenn es nicht möglich ist, eine Schnittstelle zu erstellen, die auf die gleiche Weise funktioniert.

Als foreaches zum ersten Mal in C # 1.0 eingeführt wurde (dieses Verhalten ist sicherlich nichts Neues), musste es magische Methoden verwenden, da es keine Generika gab. Die Optionen waren im Grunde:

  1. Verwenden Sie das nicht-generische IEnumerableund IEnumeratordas funktioniert mit objects, was Boxing-Werttypen bedeutet. Da die Iteration einer Liste von ints sehr schnell sein sollte und auf keinen Fall viele Werte in Garbage Boxes erzeugen sollte, ist dies keine gute Wahl.

  2. Warten Sie auf Generika. Dies würde wahrscheinlich bedeuten, .Net 1.0 (oder zumindest foreach) um mehr als 3 Jahre zu verzögern .

  3. Wende magische Methoden an.

Sie entschieden sich also für Option 3 und diese blieb aus Gründen der Abwärtskompatibilität bei uns, obwohl dies seit .NET 2.0 IEnumerable<T>auch funktioniert hätte.


Auflistungsinitialisierer können für jeden Auflistungstyp unterschiedlich aussehen. Vergleichen Sie List<T>:

public void Add(T item)

und Dictionary<TKey, TValue>:

public void Add(TKey key, TValue value)

Es kann keine einzige Schnittstelle geben, die nur das erste Formular List<T>und nur das zweite Formular unterstützt Dictionary<TKey, TValue>.


LINQ-Methoden werden in der Regel als Erweiterungsmethoden implementiert (sodass es für alle implementierenden Typen nur eine einzige Implementierung von z. B. LINQ to Object geben kann), sodass IEnumerable<T>keine Schnittstelle verwendet werden kann.


Für awaitdie, GetResult()Methode zurückgeben kann entweder voidoder irgendeine Art T. Auch hier kann es keine einzige Schnittstelle geben, die beide Funktionen unterstützt. Obwohl awaitteilweise schnittstellenbasiert: Der Kellner muss implementieren INotifyCompletionund kann auch implementieren ICriticalNotifyCompletion.

svick
quelle
1
Sind Sie sicher, dass sie Option 3 für C # 1.0 gewählt haben? Meiner Erinnerung nach war es Option 1. Mein Gedächtnis ist, dass C # eine signifikante Überlegenheit gegenüber Java behauptete, weil der C # -Compiler 'Auto-Boxing' und 'Auto-Unboxing' durchgeführt hat. Es wurde behauptet, C # habe Code wie diesen für jede Arbeit viel besser gemacht als Java, auch deswegen.
gbulmer
1
In der Dokumentation zu foreachC # 1.2 (in MSDN für C # 1.0 gibt es nichts) heißt es, dass der Ausdruck in foreach"Wertet IEnumerableeinen Typ aus, der eine GetEnumeratorMethode implementiert oder deklariert ". Und ich bin nicht sicher, was Sie damit meinen, Unboxing in C # ist immer explizit.
Svick
1
@gbulmer Und die ECMA-Spezifikation für C # vom Dezember 2001 (was bedeuten muss, dass es sich um C # 1.0 handelt) besagt auch Folgendes (§15.8.4): „Ein Typ C wird als Auflistungstyp bezeichnet, wenn er die System.IEnumerable-Schnittstelle implementiert oder implementiert das Erfassungsmuster, indem alle folgenden Kriterien erfüllt werden […] “.
Svick
1
@svick foreach(T x in sequence)wendet explizite Casts Tauf die Elemente der Sequenz an. Wenn die Sequenz eine einfache Sequenz IEnumerableund Tein Werttyp ist, wird sie entpackt, ohne dass eine explizite Umwandlung in Ihren Code geschrieben wird. Einer der hässlicheren Teile von C #.
CodesInChaos
@CodesInChaos "Auto-Unboxing" klingt ziemlich allgemein, ich habe nicht bemerkt, dass es auf diese spezielle Funktion verweist. (Ich denke, ich hätte es tun sollen, da wir darüber gesprochen haben foreach.)
SVICK