ancora confuso su covarianza e controvarianza e in / out

ok ho letto un po ‘su questo argomento su StackOverflow, ho guardato questo e questo , ma sono ancora un po’ confuso riguardo la co / contro-varianza.

da qui

La covarianza consente di sostituire un tipo “più grande” (meno specifico) in un’API in cui il tipo originale viene utilizzato solo in una posizione “output” (ad es. Come valore di ritorno). La controvarianza consente di sostituire un tipo “più piccolo” (più specifico) in un’API in cui il tipo originale viene utilizzato solo in una posizione di “input”.

so che ha a che fare con la sicurezza del tipo.

sulla cosa in/out . posso dire di usare in quando ho bisogno di scrivere ad esso, e out quando è di sola lettura. e in mezzo contro-varianza, out co-varianza. ma dalla spiegazione sopra …

e qui

Ad esempio, un List non può essere considerato come un List perché list.Add(new Apple()) è valido per Elenco ma non per List .

così non dovrebbe essere, se dovessi usare / sto andando a scrivere sull’object, deve essere più grande più generico.

so che questa domanda è stata posta ma ancora molto confusa.

Sia la covarianza che la controvarianza in C # 4.0 si riferiscono alla possibilità di utilizzare una class derivata invece della class base. Le parole chiave in / out sono suggerimenti del compilatore per indicare se i parametri del tipo saranno utilizzati per l’input e l’output.

covarianza

La covarianza in C # 4.0 è aiutata dalla parola chiave out e significa che un tipo generico che utilizza una class derivata del parametro del tipo out è OK. Quindi

 IEnumerable fruit = new List(); 

Poiché Apple è un Fruit , List può essere tranquillamente utilizzato come IEnumerable

controvarianza

La controvarianza è la parola chiave in e denota tipi di input, di solito in delegati. Il principio è lo stesso, significa che il delegato può accettare più classi derivate.

 public delegate void Func(T param); 

Ciò significa che se abbiamo un Func , può essere convertito in Func .

 Func fruitFunc = (fruit)=>{}; Func appleFunc = fruitFunc; 

Perché si chiamano co / controvarianza se sono fondamentalmente la stessa cosa?

Perché anche se il principio è lo stesso, casting sicuro da derivato a base, quando usato sui tipi di input, possiamo tranquillamente lanciare un tipo meno derivato ( Func ) su un tipo più derivato ( Func ), che ha senso, dal momento che qualsiasi funzione che porta Fruit , può anche prendere Apple .

Ho dovuto pensare a lungo e duramente su come spiegarlo bene. Spiegare è così difficile come comprenderlo.

Immagina di avere una frutta di class base. E tu hai due sottoclassi di Apple e Banana.

  Fruit / \ Banana Apple 

Crei due oggetti:

 Apple a = new Apple(); Banana b = new Banana(); 

Per entrambi questi oggetti è ansible convertirli nell’object Fruit.

 Fruit f = (Fruit)a; Fruit g = (Fruit)b; 

Puoi trattare le classi derivate come se fossero la loro class base.

Tuttavia non puoi trattare una class base come se fosse una class derivata

 a = (Apple)f; //This is incorrect 

Lo applico all’esempio della lista.

Supponi di aver creato due elenchi:

 List fruitList = new List(); List bananaList = new List(); 

Puoi fare qualcosa come questo …

 fruitList.Add(new Apple()); 

e

 fruitList.Add(new Banana()); 

perché essenzialmente li sta digitando mentre li aggiungi alla lista. Puoi pensare in questo modo …

 fruitList.Add((Fruit)new Apple()); fruitList.Add((Fruit)new Banana()); 

Tuttavia, applicando la stessa logica al caso inverso si alzano alcune bandiere rosse.

 bananaList.Add(new Fruit()); 

equivale a

 bannanaList.Add((Banana)new Fruit()); 

Poiché non puoi trattare una class base come una class derivata, ciò genera errori.

Nel caso in cui la tua domanda fosse il motivo per cui questo causa errori, lo spiegherò anche io.

Ecco la class Fruit

 public class Fruit { public Fruit() { a = 0; } public int A { get { return a; } set { a = value } } private int a; } 

ed ecco la class Banana

 public class Banana: Fruit { public Banana(): Fruit() // This calls the Fruit constructor { // By calling ^^^ Fruit() the inherited variable a is also = 0; b = 0; } public int B { get { return b; } set { b = value; } } private int b; } 

Quindi immagina di aver creato nuovamente due oggetti

 Fruit f = new Fruit(); Banana ba = new Banana(); 

ricorda che Banana ha due variabili “a” e “b”, mentre Fruit ne ha solo una, “a”. Quindi quando lo fai …

 f = (Fruit)b; fA = 5; 

Crei un object Fruit completo. Ma se dovessi fare questo …

 ba = (Banana)f; ba.A = 5; ba.B = 3; //Error!!!: Was "b" ever initialized? Does it exist? 

Il problema è che non si crea una class Banana completa. Tutti i dati membri sono dichiarati / inizializzati.

Ora che sono tornato dalla doccia e mi sono procurato uno spuntino dove diventa un po ‘complicato.

Con il senno di poi avrei dovuto lasciare cadere la metafora quando avanzavo nella roba complicata

facciamo due nuove classi:

 public class Base public class Derived : Base 

Possono fare tutto quello che vuoi

Ora consente di definire due funzioni

 public Base DoSomething(int variable) { return (Base)DoSomethingElse(variable); } public Derived DoSomethingElse(int variable) { // Do stuff } 

Questo è un po ‘come il modo in cui “out” funziona, dovresti sempre essere in grado di usare una class derivata come se fosse una class base, lasciala applicare a un’interfaccia

 interface MyInterface { T MyFunction(int variable); } 

La differenza chiave tra out / in è quando il Generico è usato come un tipo di ritorno o un parametro di metodo, questo è il primo caso.

consente di definire una class che implementa questa interfaccia:

 public class Thing: MyInterface { } 

quindi creiamo due oggetti:

 MyInterface base = new Thing; MyInterface derived = new Thing; 

Se tu fossi fare questo:

 base = derived; 

Si otterrebbe un errore del tipo “imansible convertire implicitamente da …”

Hai due possibilità, 1) convertirle esplicitamente o, 2) dire al compilatore di convertirle implicitamente.

 base = (MyInterface)derived; // #1 

o

 interface MyInterface // #2 { T MyFunction(int variable); } 

Il secondo caso entra in gioco se la tua interfaccia appare così:

 interface MyInterface { int MyFunction(T variable); // T is now a parameter } 

collegandolo nuovamente alle due funzioni

 public int DoSomething(Base variable) { // Do stuff } public int DoSomethingElse(Derived variable) { return DoSomething((Base)variable); } 

si spera che si veda come la situazione si è invertita, ma è essenzialmente lo stesso tipo di conversione.

Usando di nuovo le stesse classi

 public class Base public class Derived : Base public class Thing: MyInterface { } 

e gli stessi oggetti

 MyInterface base = new Thing; MyInterface derived = new Thing; 

se provi a impostarli uguali

 base = derived; 

il tuo compilatore ti urlerà di nuovo, hai le stesse opzioni di prima

 base = (MyInterface)derived; 

o

 interface MyInterface //changed { int MyFunction(T variable); // T is still a parameter } 

Fondamentalmente si usa quando il generico verrà usato solo come tipo di ritorno dei metodi di interfaccia. Usa dentro quando sarà usato come parametro Method. Le stesse regole si applicano anche quando si usano i delegati.

Ci sono strane eccezioni ma non mi preoccuperò di loro qui.

Scusa per eventuali errori incuranti in anticipo =)

La covarianza è abbastanza facile da capire. È naturale. La controvarianza è più confusa.

Dai un’occhiata da vicino a questo esempio da MSDN . Guarda come SortedList si aspetta un IComparer, ma stanno passando in ShapeAreaComparer: IComparer. The Shape è il tipo “più grande” (è nella firma del callee, non nel chiamante), ma la contravarianza consente al tipo “più piccolo” – il Cerchio – di essere sostituito per qualsiasi punto di ShapeAreaComparer che normalmente assumerebbe una forma.

Spero possa aiutare.

In Jons words:

La covarianza consente di sostituire un tipo “più grande” (meno specifico) in un’API in cui il tipo originale viene utilizzato solo in una posizione “output” (ad es. Come valore di ritorno). La controvarianza consente di sostituire un tipo “più piccolo” (più specifico) in un’API in cui il tipo originale viene utilizzato solo in una posizione di “input”.

Ho trovato la sua spiegazione confusa all’inizio – ma aveva senso per me una volta essere sostituita è enfatizzata, combinata con l’esempio della guida di programmazione C #:

 // Covariance. IEnumerable strings = new List(); // An object that is instantiated with a more derived type argument // is assigned to an object instantiated with a less derived type argument. // Assignment compatibility is preserved. IEnumerable objects = strings; // Contravariance. // Assume that the following method is in the class: // static void SetObject(object o) { } Action actObject = SetObject; // An object that is instantiated with a less derived type argument // is assigned to an object instantiated with a more derived type argument. // Assignment compatibility is reversed. Action actString = actObject; 

Il delegato del convertitore mi aiuta a capirlo:

 delegate TOutput Converter(TInput input); 

TOutput rappresenta la covarianza in cui un metodo restituisce un tipo più specifico .

TInput rappresenta la controvarianza in cui un metodo viene passato ad un tipo meno specifico .

 public class Dog { public string Name { get; set; } } public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } } public static Poodle ConvertDogToPoodle(Dog dog) { return new Poodle() { Name = dog.Name }; } List dogs = new List() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } }; List poodles = dogs.ConvertAll(new Converter(ConvertDogToPoodle)); poodles[0].DoBackflip(); 

Prima di passare all’argomento, facciamo un rapido aggiornamento:

Il riferimento alla class base può contenere un object di class derivato MA non viceversa.

Covarianza : Covariance consente di passare un object di tipo derivato in cui è previsto un object di tipo base Covariance può essere applicato a delegati, generici, array, interfaccia, ecc.

Controvarianza: controvarianza viene applicata ai parametri. Consente a un metodo con il parametro di una class base di essere assegnato a un delegato che si aspetta il parametro di una class derivata

Dai un’occhiata al semplice esempio qui sotto:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace CovarianceContravarianceDemo { //base class class A { } //derived class class B : A { } class Program { static A Method1(A a) { Console.WriteLine("Method1"); return new A(); } static A Method2(B b) { Console.WriteLine("Method2"); return new A(); } static B Method3(B b) { Console.WriteLine("Method3"); return new B(); } public delegate A MyDelegate(B b); static void Main(string[] args) { MyDelegate myDel = null; myDel = Method2;// normal assignment as per parameter and return type //Covariance, delegate expects a return type of base class //but we can still assign Method3 that returns derived type and //Thus, covariance allows you to assign a method to the delegate that has a less derived return type. myDel = Method3; A a = myDel(new B());//this will return a more derived type object which can be assigned to base class reference //Contravariane is applied to parameters. //Contravariance allows a method with the parameter of a base class to be assigned to a delegate that expects the parameter of a derived class. myDel = Method1; myDel(new B()); //Contravariance, } } }