2016-04-20 10 views
4

Ich hatte beim Umgang mit kovarianten Interfaces einen kompletten WTF-Moment.Generische Kovarianz mit Interfaces - Seltsamer Verhaltenswiderstand zwischen "is" - und "=" -Operatoren

Beachten Sie Folgendes:

class Fruit { } 
class Apple : Fruit { } 

interface IBasket<out T> { } 

class FruitBasket : IBasket<Fruit> { } 
class AppleBasket : IBasket<Apple> { } 

Hinweis:

  • AppleBasketerbt nicht vonFruitBasket.
  • IBasket ist kovariant.

Später im Skript, schreiben Sie:

FruitBasket fruitBasket = new FruitBasket(); 
AppleBasket appleBasket = new AppleBasket(); 

Log(fruitBasket is IBasket<Fruit>); 
Log(appleBasket is IBasket<Apple>); 

... und wie man erwarten würde, ist der Ausgang:

true 
true 

Beachten Sie jedoch,, den folgenden Code:

AppleBasket appleBasket = new AppleBasket(); 

Log(appleBasket is IBasket<Fruit>);  

Sie würden erwarten, dass es ausgegeben wird true, richtig? Nun, Sie sind falsch - zumindest mit meinem Compiler trotzdem:

false  

Das ist seltsam, aber vielleicht ist es die Durchführung einer implizite Konvertierung, ähnlich wie eine int zu einem long konvertieren. Eine int ist keine Art von long, aber eine lange kann den Wert eines int implizit zugewiesen werden.

JEDOCH betrachten diesen Code:

IBasket<Fruit> basket = new AppleBasket(); // implicit conversion? 

Log(basket is IBasket<Fruit>); 

Dieser Code läuft ganz gut - keine Compiler-Fehler oder Ausnahmen - auch wenn wir gelernt vorher, dass ein AppleBasket nicht eine Art von IBasket<Fruit> ist. Sofern es keine dritte Option gibt, muss eine implizite Konvertierung in der Zuweisung vorgenommen werden.

Sicher basket - erklärt als IBasket<Fruit> - MUST eine Instanz IBasket<Fruit> sein ... ich meine, das ist, was es war, als erklärt. Recht?

Aber nein, nach dem is Operator, Sie sind wieder falsch! Es gibt:

false 

... Bedeutung IBasket<Fruit> fruit keine Instanz IBasket<Fruit> ist ... Huh?

...Gemeint ist die folgende Eigenschaft:

IBasket<Fruit> FruitBasket { get { ... } } 

manchmal etwas zurückgeben, die beide nicht null ist, und ist keine Instanz von IBasket<Fruit>.


WEITERE, sagt ReSharper mir, dass appleBasket is IBasket<Fruit> redundant ist, dass appleBasket ist immer der dargebotenen Art, und kann sicher mit appleBasket != null ersetzt werden ... ReSharper bekam auch etwas falsch gemacht?

Also, was ist hier los? Ist das nur meine Version von C# (Unity 5.3.1p4 - It's Unity's eigener Zweig von Mono, basierend auf .NET 2.0), der eine Nuss ist?

+3

Uh, welche Version von .NET laufen Sie? 'appleBasket is IBasket ' druckt 'true' für mich. Siehe [die Geige] (https://dotnetfiddle.net/aiNzDA), um zu replizieren. – Rob

+1

für mich funktioniert es, auch ich bin interessiert zu sehen, welche Version von '.Net'. –

+0

Die Version von Mono, die in Unity 5.3.1p4 zur Verfügung gestellt wird - Es ist Unity's eigener Zweig von Mono, basierend auf .NET 2.0 – Hatchling

Antwort

1

Aufgrund Ihrer Kommentare ist es keine kleine Überraschung, dass die Kovarianz nicht richtig unterstützt wird ... sie wurde in C# 4 hinzugefügt.

Was IST unglaublich überraschend ist, dass es überhaupt kompiliert, wenn es einen Port Targeting basiert weg von .NET 2.0

+0

Ich schätze, ich gehe in den Unity-Fehlerberichtsland. Wünsch mir Glück! – Hatchling

+1

Das könnte ein langer und mühsamer Kampf sein. Hoffe, es endet gut für Sie :) –

+0

Von Unity QA: "Ich habe es geschafft, das Problem zu reproduzieren. Zum Glück wurde es gelöst und ist nicht mehr reproduzierbar mit 5.5.0a4 Version von Unity. Bitte testen Sie das Problem, wenn Unity 5.5 öffentlich sein wird verfügbar und lassen Sie mich wissen, wenn das Problem immer noch vorhanden ist. " – Hatchling

0

Sie sind in der Lage dies aus einem Grund zu erklären:

IBasket<Fruit> basket = new AppleBasket(); 

Da diese Schnittstelle keine Methoden hat, die auf <T> verweisen.

Es gibt nichts, vor dem der Compiler Sie schützen kann, weil Sie nur die Typen deklarieren können. Du kannst nichts mit ihnen machen. Versuchen Sie, eine Methode zur Schnittstelle hinzufügen:

interface IBasket<out T> 
{ 
    void Add(T item); 
} 

Und das ist, wo der Compiler einen Fehler zu erhöhen, zu verlangen, dass Sie die Kovarianz (out) von der Oberfläche entfernen. Wenn Sie es entfernen, dann wird dies jetzt nicht kompilieren:

IBasket<Fruit> basket = new AppleBasket() 

denn dann würden Sie in der Lage sein, nicht Apple Objekte zu einem IBasket<Apple> hinzuzufügen.

+0

Tatsächlich hat das Deklarieren einer OUTPUT-Methode in IBasket keine Auswirkung auf dieses Verhalten. Zum Beispiel: Schnittstelle IBasket {T Item {get; }} Die Angabe einer Methode, bei der T als Eingabe verwendet wird (wie die von Ihnen angegebene), erzeugt jedoch einen Fehler. In diesem Fall müssten Sie verwenden. – Hatchling