2016-01-13 7 views
5

folgendes covariant generische SchnittstelleKovariante Schnittstelle mehrmals implementieren: Ist dieses Verhalten richtig definiert?

public interface IContainer<out T> 
{ 
    T Value { get; } 
} 

Da können wir eine Klasse erstellen, die diese Schnittstelle mehrmals für mehrere generische Typen implementiert. In dem Szenario, das mich interessiert, teilen diese generischen Typen einen gemeinsamen Basistyp.

public interface IPrint 
{ 
    void Print(); 
} 
public class PrintA : IPrint 
{ 
    public void Print() 
    { 
     Console.WriteLine("A"); 
    } 
} 
public class PrintB : IPrint 
{ 
    public void Print() 
    { 
     Console.WriteLine("B"); 
    } 
} 

public class SuperContainer : IContainer<PrintA>, IContainer<PrintB> 
{ 
    PrintA IContainer<PrintA>.Value => new PrintA(); 
    PrintB IContainer<PrintB>.Value => new PrintB(); 
} 

Nun wird es interessant, wenn diese Klasse durch eine Referenz des Typs mit IContainer<IPrint>.

Dies kompiliert und läuft ohne Problem und druckt "A". Was ich in den spec gefunden:

Die Implementierung eines bestimmten Schnittstelle Mitglieds IM, wo ich die Schnittstelle in der das Element M deklariert ist, durch bestimmt wird jede Klasse oder Struktur S Prüfung starten mit C und jede nachfolgenden Basisklasse von C wiederholt wird, bis eine Übereinstimmung befindet:

  • Wenn S eine Erklärung einer explizites Schnittstellenelement Implementierung enthält , die I und M übereinstimmt, dann ist dieses Element die Umsetzung von IM
  • Andernfalls, wenn S eine Deklaration eines nicht-statischen öffentlichen Mitglied enthält die M übereinstimmt, dann dieses Mitglied ist die Implementierung von IM

Der erste Aufzählungspunkt erscheint relevant zu sein, denn die Schnittstelle Implementierungen sind explizit. Es sagt jedoch nichts darüber aus, welche Implementierung ausgewählt wird, wenn mehrere Kandidaten vorhanden sind.

Es wird noch interessanter, wenn wir eine öffentliche poperty für die IContainer<PrintA> Implementierung verwenden:

public class SuperContainer : IContainer<PrintA>, IContainer<PrintB> 
{ 
    public PrintA Value => new PrintA(); 
    PrintB IContainer<PrintB>.Value => new PrintB(); 
} 

nun nach oben spec, weil es eine explizite Schnittstellenimplementierung durch IContainer<PrintB> ist, würde ich dies zu drucken erwarten "B". Es verwendet jedoch stattdessen die öffentliche Eigenschaft und druckt immer noch "A".

Ähnlich, wenn ich stattdessen IContainer<PrintA> explizit und IContainer<PrintB> durch öffentliche Eigenschaft implementieren, druckt es immer noch "A".

Es scheint, dass das einzige, von dem die Ausgabe abhängt, die Reihenfolge ist, in der die Schnittstellen deklariert werden. Wenn ich die Deklaration zu

public class SuperContainer : IContainer<PrintB>, IContainer<PrintA> 

ändern, druckt alles "B"!

Welcher Teil der Spezifikation definiert dieses Verhalten, wenn es überhaupt richtig definiert ist?

+1

Ich würde denken, dass es irgendwo angegeben ist, aber ich sehe es nicht. Eric Lippert hatte eine [bleg] (https://blogs.msdn.microsoft.com/ericlippert/2007/11/09/covariance-and-contravariance-in-c-part-ten-deling-with-ambiguity/) a Vor langer Zeit dachte jemand über dieses Problem nach. –

Antwort

0

Ich kann es in der Spezifikation nicht finden, aber was Sie sehen, wird erwartet.IContainer<PrintA> und IContainer<PrintB> haben unterschiedliche voll qualifizierte Namen (nicht in der Lage zu finden, Spezifikation, wie diese FQN gebildet wird) und so erkennt der Compiler SuperContainer als eine implementierende Klasse von zwei verschiedenen Schnittstellen, die jeweils eine void Print();-Methode haben.

So haben wir zwei verschiedene Schnittstellen, die jeweils eine Methode mit der gleichen Signatur enthalten. Wie Sie im spec (13.4.2) verbunden sind, wird die Implementierung von Print() zuerst ausgewählt, indem Sie auf IContainer<PrintA> schauen, auf der Suche nach einer richtigen Zuordnung und dann Blick auf IContainer<PrintB>.

Da eine richtige Zuordnung in IContainer<PrintA> gefunden wurde, wird verwendet in IContainer<PrintA>.Print()SuperContainer ‚s implimentation von IContainer<PrintB>.

Aus demselben spec (zuunterst befindet):

Die Mitglieder einer Basisklasse in Interface-Mapping teilnehmen. Im Beispiel

interface Interface1 
{ 
    void F(); 
} 
class Class1 
{ 
    public void F() {} 
    public void G() {} 
} 
class Class2: Class1, Interface1 
{ 
    new public void G() {} 
} 

die Methode F in Class1 in Class2 Implementierung von Interface1 verwendet.

Also am Ende, ja die Reihenfolge bestimmt, welche Print() Methode aufgerufen wird.

+1

Genau aus welchem ​​Text in der Spezifikation kann man "die Implementierung von Print()" ableiten, man wählt zuerst "IContainer " aus, sucht nach einer korrekten Zuordnung und schaut dann 'IContainer '. "? – Steven

Verwandte Themen