wciąż zdezorientowany o kowariancji i kontrawariancji i in/out

Ok przeczytałem trochę na ten temat na stackoverflow, obejrzałem to & to, ale nadal trochę zdezorientowany co / kontra-wariancji.

From here

Kowariancja pozwala na "większe" (mniej specyficzny) typ, który należy zastąpić w API, gdzie tylko oryginalny typ jest używany w pozycji "wyjściowej" (np. jako wartość zwracana). Kontrawariancja pozwala "mniejszy" (bardziej konkretny) typ, który ma być podstawione w API, gdzie typ Oryginalny jest stosowany tylko w pozycja "input".

Wiem, że to ma związek z bezpieczeństwem typu.

O in/out rzeczy. mogę powiedzieć, że używam in, gdy muszę do niego napisać, i out, gdy tylko do odczytu. i in oznacza kontra wariancję, out współ wariancję. ale z powyższego wyjaśnienia...

I tutaj

Na przykład, a List<Banana> nie może być traktowany jako List<Fruit>, ponieważ list.Add(new Apple()) jest ważna dla Lista, ale nie dla List<Banana>.

Więc nie powinno be, gdybym miał użyć in / mam zamiar napisać do obiektu, musi być większy bardziej ogólny.

Wiem, że to pytanie zostało zadane, ale nadal bardzo zdezorientowane.

Author: Community, 2010-08-10

5 answers

Zarówno KOWARIANCJA, jak i kontrawariancja w C# 4.0 odnoszą się do możliwości używania klasy pochodnej zamiast klasy bazowej. Słowa kluczowe wejścia/wyjścia są podpowiedziami kompilatora, aby wskazać, czy parametry typu będą używane do wprowadzania i wyjścia.

Kowariancja

Kowariancja w C# 4.0 jest wspomagana przez słowo kluczowe out i oznacza, że typ generyczny wykorzystujący pochodną klasy parametru typu out jest OK. Stąd

IEnumerable<Fruit> fruit = new List<Apple>();

Ponieważ Apple jest Fruit, List<Apple> może być bezpiecznie stosowany jako IEnumerable<Fruit>

Kontrawariancja

Contravariance jest słowem kluczowym in i oznacza typy danych wejściowych, zwykle w delegatach. Zasada jest taka sama, oznacza to, że delegat może zaakceptować więcej klas pochodnych.

public delegate void Func<in T>(T param);

Oznacza to, że jeśli mamy Func<Fruit>, można ją przekształcić do Func<Apple>.

Func<Fruit> fruitFunc = (fruit)=>{};
Func<Apple> appleFunc = fruitFunc;

Dlaczego nazywa się je ko / kontrawariancją, jeśli są zasadniczo tym samym?

Ponieważ chociaż zasada jest taka sama, bezpieczne odlewanie od pochodnej do bazy, w przypadku użycia na typach wejściowych możemy bezpiecznie przenieść Typ mniej Pochodny (Func<Fruit>) na typ bardziej Pochodny (Func<Apple>), co ma sens, ponieważ każda funkcja przyjmująca Fruit może również przyjmować Apple.

 34
Author: Igor Zevaka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-10 04:16:16

Musiałem długo i ciężko zastanowić się, jak to dobrze wyjaśnić. Wyjaśnianie tego wydaje się być tak samo trudne jak zrozumienie tego.

Wyobraź sobie, że masz owoc klasy podstawowej. I masz dwie podklasy jabłko i banan.
     Fruit
      / \
Banana   Apple

Tworzysz dwa obiekty:

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

Dla obu tych obiektów można wpisać je do obiektu Fruit.

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

Możesz traktować klasy pochodne tak, jakby były ich klasami bazowymi.

Nie można jednak traktować klasy bazowej jak była to klasa pochodna

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

Zastosujmy to do przykładu listy.

Załóżmy, że stworzyłeś dwie listy:

List<Fruit> fruitList = new List<Fruit>();
List<Banana> bananaList = new List<Banana>();
Możesz zrobić coś takiego...
fruitList.Add(new Apple());

I

fruitList.Add(new Banana());

Ponieważ jest to zasadniczo typecasting je jak dodać je do listy. Możesz o tym myśleć w ten sposób...

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

Jednak zastosowanie tej samej logiki do odwrotnej sprawy budzi pewne wątpliwości.

bananaList.Add(new Fruit());

Jest tym samym co

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

Ponieważ nie możesz traktuj klasę bazową jak klasę pochodną, która powoduje błędy.

Na wypadek, gdyby twoje pytanie brzmiało, dlaczego powoduje to błędy, to też wyjaśnię.

Oto Klasa owoców

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

A oto Klasa 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;
}
Wyobraź sobie, że znowu stworzyłeś dwa obiekty]}
Fruit f = new Fruit();
Banana ba = new Banana();

Pamiętaj, że banan ma dwie zmienne "a" i "b", podczas gdy owoc ma tylko jedną, "a". Więc kiedy to zrobisz...

f = (Fruit)b;
f.A = 5;

Tworzysz kompletny obiekt owocowy. Ale jeśli miałeś to zrobić...

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

Problem polega na tym, że nie tworzy się kompletnej klasy bananów.Nie wszystkie elementy danych są zadeklarowane / zainicjalizowane.

Teraz, kiedy wróciłem z prysznica i kupiłem sobie przekąskę heres, gdzie robi się trochę skomplikowane.

Z perspektywy czasu powinienem był porzucić metaforę, gdy wdawałem się w skomplikowane sprawy

Stwórzmy dwie nowe klasy:

public class Base
public class Derived : Base
Mogą robić co chcesz]}

Teraz zdefiniujmy dwa funkcje

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

Jest to coś w rodzaju tego, jak działa "out", powinieneś zawsze być w stanie używać klasy pochodnej, tak jakby była klasą bazową, zastosujmy to do interfejsu

interface MyInterface<T>
{
    T MyFunction(int variable);
}

Kluczowa różnica między out/in polega na tym, że Rodzajnik jest używany jako typ zwracany lub parametr metody, tak było w poprzednim przypadku.

Pozwala zdefiniować klasę, która implementuje ten interfejs:

public class Thing<T>: MyInterface<T> { }

Następnie tworzymy dwa obiekty:

MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;

If you were do to:

base = derived;

Pojawi się błąd w stylu " nie można bezwarunkowo konwertować z..."

Masz dwie opcje, 1) jawnie je przekonwertować lub, 2) Powiedz kompilator, aby implicite je przekonwertować.

base = (MyInterface<Base>)derived; // #1

Lub

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

Drugi przypadek wchodzi do gry, Jeśli interfejs wygląda tak:

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

Odnoszący się ponownie do dwóch funkcji

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

Mam nadzieję, że widzisz, jak sytuacja się odwróciła, ale zasadniczo jest to ten sam typ nawrócenie.

Używanie tych samych klas ponownie

public class Base
public class Derived : Base
public class Thing<T>: MyInterface<T> { }

I te same obiekty

MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;

Jeśli spróbujesz ustawić je równe

base = derived;

Twój komp będzie krzyczeć na ciebie ponownie, masz te same opcje jak wcześniej

base = (MyInterface<Base>)derived;

Lub

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

Zasadniczo Używaj, gdy generic będzie używany tylko jako typ zwracający metody interfejsu. Użyj W, gdy ma być używany jako parametr metody. Te same zasady obowiązują przy korzystaniu z delegatów też.

Są dziwne wyjątki, ale nie będę się o nie martwić.

Przepraszam za nieostrożne błędy z góry=)

 49
Author: Akinos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-10 15:14:48

Kowariancja jest dość łatwa do zrozumienia. To naturalne. Kontrawariancja jest bardziej zagmatwana.

Przyjrzyj się temu przykładowi z MSDN . Zobacz, jak SortedList oczekuje IComparer, ale przechodzą one w Postaciareacomparer: IComparer. Kształt jest typem " większym "(jest w sygnaturze callee, a nie callee), ale kontrawariancja pozwala na zastąpienie" mniejszego " typu - Okręgu wszędzie w kształcie, które normalnie zajęłoby Kształt.

Mam nadzieję, że to pomoże.

 7
Author: Richard Hein,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-10 04:29:43

W słowach Jons:

Kowariancja pozwala na zastąpienie" większego "(mniej specyficznego) typu w interfejsie API, gdzie oryginalny typ jest używany tylko w pozycji" wyjściowej " (np. jako wartość zwracana). Contravariance pozwala na zastąpienie" mniejszego "(bardziej specyficznego) typu w interfejsie API, gdzie oryginalny typ jest używany tylko w pozycji" input".

Na początku wydawało mi się, że jego wyjaśnienie jest mylące - ale dla mnie kiedyś miało to sens, aby być podmienionym jest podkreślane, w połączeniu z przykładem z podręcznika programowania C#:

// Covariance.   
IEnumerable<string> strings = new List<string>();  
// 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<object> objects = strings;

// Contravariance.             
// Assume that the following method is in the class:   
// static void SetObject(object o) { }   
Action<object> 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<string> actString = actObject;    

Konwerter pomaga mi to zrozumieć:

delegate TOutput Converter<in TInput, out TOutput>(TInput input);

TOutput reprezentuje kowariancję, gdzie metoda zwraca bardziej specyficzny typ .

TInput reprezentuje kontrasty, gdzie metoda jest przekazywana mniej specyficzny typ.

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<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();
 5
Author: woggles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-06 03:21:00

Zanim przejdziemy do tematu, zróbmy szybkie odświeżenie:

Odwołanie do klasy bazowej może zawierać obiekt klasy pochodnej, ale nie odwrotnie.

Kowariancja : KOWARIANCJA pozwala przekazać obiekt typu pochodnego, w którym oczekuje się obiektu typu podstawowego KOWARIANCJA może być zastosowana na delegate, generic, array, interface, itp.

Kontrawariancja: Przeciwstawność jest stosowana do parametrów. Pozwala na przypisanie metody z parametrem klasy bazowej do delegata że oczekuje parametru pochodnej klasy

Spójrz na prosty przykład poniżej:

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, 

        }
    }
}
 3
Author: ABajpai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-14 11:38:19