2016-07-20 6 views
0

Betrachten Sie eine Schnittstelle mit einem kovarianten Typ T. Ich untersuche den Fall, in dem Eigenschaften in allen abgeleiteten Klassen dieser Schnittstelle, die T verwenden, schreibgeschützt und kovariant sind, wenn es sich um eine generische Klasse handelt. Angenommen, diese Schnittstelle definiert dann eine Methode, die T als Argumenttyp verwendet. Was für Verstöße erlaubt es?Welche Verstöße gibt es bei Verwendung eines kovarianten Typs in einer kontravarianten Position?

Betrachten wir zum Beispiel:

interface ICov<out T> { 
    void maybe_safe_set(T v); 
} 
class ImplCov<T> : ICov<T> { 
    public readonly T a; 
    public readonly IEnumerable<T> b; 
    public readonly IEnumerable<IEnumerable<T>> c; 
    // public readonly IList<T> d; // but not this 

    public void maybe_safe_set(T v) { 
    // do things that can't modify state: the type of our 
    // readonly, covariant IEnumerable members can't be modified 
    } 
} 

In C#, erhalte ich die Fehlermeldung:

Invalid variance: The type parameter 'T' must be contravariantly valid on 'ConsoleApplication.ICov.maybe_safe_set(T)'. 'T' is covariant.

was nicht verwunderlich ist, da T in einer kontra Position gefunden wird. Ich kann mir jedoch keine Verletzung vorstellen, die hier auftreten kann.

+0

Covariance bedeutet, dass der Typ nur von der Schnittstelle konsumiert wird und niemals an ihn übergeben wird. Stellen Sie sich das so vor, das 'out'-Schlüsselwort bedeutet, dass der Typ nur aus der Schnittstelle kommen sollte, aber Sie haben es als Argument der' maybe_safe_set'-Methode eingefügt. "T" kann stattdessen als kontravariant definiert werden. Die Schnittstelle ist ein Vertrag und sie weiß nicht, was eine bestimmte Implementierung tun könnte. – juharr

+2

'öffentliche statische Klasse C {öffentlicher statischer T-Wert}' und 'public void may_safe_set (T v) {C .Value = v}' und '((ICov ) neue ImplCov ()). Mayable_safe_set (neues Objekt()) '. Jetzt versuchen Sie, 'object'' C .Value' vom Typ 'string' zuzuordnen. – PetSerAl

+1

Wenn das erlaubt wäre, könntest du eine 'ImpleConv 'nach' ICov 'umwandeln und dann könntest du' may_safe_set' nennen und passierst etwas, das keine 'string' ist und dann einen Runtime Type Fehler haben. Es hat also wirklich nichts mit den Implementierungsdetails der implementierenden Klasse zu tun. – juharr

Antwort

1

Sie:

interface ICov<out T> // BAD! 
{ 
    void maybe_safe_set(T v); 
} 

Hier kommt das Problem. Wie üblich haben wir:

class Animal { /* ... */ } 
class Dog : Animal { public void Woof() { } /* ... */ } 
class Cat : Animal { /* ... */ } 

Dann betrachten:

class Impl : ICov<Dog> 
{ 
    public void maybe_safe_set(Dog v) 
    { 
    v.Woof(); // our 'Dog' v can really bark 
    } 
} 

die gerade übersetzt werden können.

Dann folgt aus:

var impl1 = new Impl(); 
ICov<Dog> impl2 = impl1; // OK, implements that 
ICov<Animal> impl3 = impl2; // OK, you claim interface is covariant ('out')! 

var badAnimal = new Cat(); 
impl3.maybe_safe_set(badAnimal); // ICov<Animal> takes in Animal, right? 

// oh my God, you mad a 'Cat' bark! 

Es ist immer das gleiche Beispiel, wenn Leute über Co- und Kontra fragen.

+0

Ich dachte, ich hätte ein gutes Beispiel. Aber du kannst Katzenbarke nicht schlagen. Dieser wird für zukünftiges Plagiat für immer abgelegt. –

+0

_ "Es ist immer das gleiche Beispiel, wenn Leute nach Ko- und Kontravarianz fragen" _ - ja, ist es immer. Was impliziert die Frage: Warum sollte man die Frage _again_ beantworten, anstatt sie als Duplikat einer dieser Fragen, die das gleiche Beispiel haben, zu markieren? –

0

Ich weiß nicht über alle anderen, aber ich finde dieses Zeug auch verwirrend. Um es zu verstehen, muss ich ein Beispiel erstellen, um zu sehen, welche Verletzungen der Compiler verhindert.

Betrachten wir es zuerst ohne die Methode in der Schnittstelle. Das ist alles gültig:

interface ICov<out T> {} 

public class BaseClass { } 
public class InheritedClass: BaseClass { } 

ICov<BaseClass> x = new MyCov<InheritedClass>(); 

Da T covariant ist, eine Variable vom Typ ICov<BaseClass> auf eine Instanz MyCov<T> beziehen kann, wo T von BaseClass ableitet.

Was nun, wenn wir eine Methode hinzufügen könnte, die T an die Schnittstelle vom Typ arbeitet (was der Compiler verhindert?)

interface ICov<out T> 
{ 
    List<T> ListOfT { get; set; } 
    // ^^^ compiler doesn't like this. 
} 

public class BaseClass { } 
public class InheritedClass: BaseClass { } 

public class MyCov<T> : ICov<T> { 
    public List<T> ListOfT { get; set; } = new List<T>(); 
} 

Jetzt können wir das Problem genau sehen, würde dies schaffen:

var usesInheritedClass = new MyCov<InheritedClass>(); 

//This is legal, because the type parameter in ICov is covariant 
ICov<BaseClass> usesBaseClass = usesInheritedClass; 

//Here's where it goes bad. 
usesBaseClass.ListOfT.Add(new BaseClass()); 

usesInheritedClass enthält eine Liste von InheritedClass. Aber weil ich es als ICov<BaseClass> einwerfen kann, kann ich jetzt eine BaseClass zu einer Liste von InheritedClass hinzufügen, die nicht funktionieren kann.

Also, obwohl der Compilerfehler verwirrend ist, ist es an der richtigen Stelle. Es versucht nicht, meine Downstream-Fehler zu beheben. Es verhindert diese nachgelagerten Fehler vollständig, indem es ein Szenario verhindert, in dem diese Typen verwirrt werden können.

+0

Eigentlich habe ich "readonly" und "IEnumerable" in diese Frage gebracht, um diese genaue Verletzung zu vermeiden! Ich dachte, der Staat sei das Problem, also versuchte ich, es so unveränderlich wie möglich zu machen, aber die getippten Methoden bleiben, wie Jeppe aufzeigt, problematisch. – concat

Verwandte Themen