Литье T интерфейса & lt; T & gt; к интерфейсу & lt; baseT & gt; [Дубликат]

В Java все переменные, которые вы объявляете, на самом деле являются «ссылками» на объекты (или примитивы), а не самими объектами.

При попытке выполнить один метод объекта , ссылка просит живой объект выполнить этот метод. Но если ссылка ссылается на NULL (ничего, нуль, void, nada), то нет способа, которым метод будет выполнен. Тогда runtime сообщит вам об этом, выбросив исключение NullPointerException.

Ваша ссылка «указывает» на нуль, таким образом, «Null -> Pointer».

Объект живет в памяти виртуальной машины пространство и единственный способ доступа к нему - использовать ссылки this. Возьмем этот пример:

public class Some {
    private int id;
    public int getId(){
        return this.id;
    }
    public setId( int newId ) {
        this.id = newId;
    }
}

И в другом месте вашего кода:

Some reference = new Some();    // Point to a new object of type Some()
Some otherReference = null;     // Initiallly this points to NULL

reference.setId( 1 );           // Execute setId method, now private var id is 1

System.out.println( reference.getId() ); // Prints 1 to the console

otherReference = reference      // Now they both point to the only object.

reference = null;               // "reference" now point to null.

// But "otherReference" still point to the "real" object so this print 1 too...
System.out.println( otherReference.getId() );

// Guess what will happen
System.out.println( reference.getId() ); // :S Throws NullPointerException because "reference" is pointing to NULL remember...

Это важно знать - когда больше нет ссылок на объект (в пример выше, когда reference и otherReference оба указывают на null), тогда объект «недоступен». Мы не можем работать с ним, поэтому этот объект готов к сбору мусора, и в какой-то момент VM освободит память, используемую этим объектом, и выделит другую.

123
задан a14m 30 January 2015 в 23:45
поделиться

10 ответов

Допустим, у вас есть класс Person и класс, который вытекает из него, Учитель. У вас есть некоторые операции, которые принимают аргумент IEnumerable<Person>. В вашем классе School у вас есть метод, который возвращает IEnumerable<Teacher>. Ковариация позволяет вам напрямую использовать этот результат для методов, которые принимают IEnumerable<Person>, заменяя более производный тип для менее производного (более общего) типа. Контравариантность, контр-интуитивно, позволяет использовать более общий тип, где указан более производный тип. См. Также https://msdn.microsoft.com/en-us/library/dd799517.aspx

public class Person 
{
     public string Name { get; set; }
} 

public class Teacher : Person { } 

public class MailingList
{
    public void Add(IEnumerable<out Person> people) { ... }
}

public class School
{
    public IEnumerable<Teacher> GetTeachers() { ... }
}

public class PersonNameComparer : IComparer<Person>
{
    public int Compare(Person a, Person b) 
    { 
        if (a == null) return b == null ? 0 : -1;
        return b == null ? 1 : Compare(a,b);
    }

    private int Compare(string a, string b)
    {
        if (a == null) return b == null ? 0 : -1;
        return b == null ? 1 : a.CompareTo(b);
    }
}

...

var teachers = school.GetTeachers();
var mailingList = new MailingList();

// Add() is covariant, we can use a more derived type
mailingList.Add(teachers);

// the Set<T> constructor uses a contravariant interface, IComparer<T>,
// we can use a more generic type than required.  See https://msdn.microsoft.com/en-us/library/8ehhxeaf.aspx for declaration syntax
var teacherSet = new SortedSet<Teachers>(teachers, new PersonNameComparer());
87
ответ дан tvanfosson 18 August 2018 в 02:26
поделиться
  • 1
    Учитель как человек (человек)? Это не пример реального мира ... – Filip Bartuzi 5 December 2014 в 00:34
  • 2
    @FilipBartuzi - если, как и я, когда я написал этот ответ, вы были наняты в Университете, что является примером для реального мира. – tvanfosson 5 December 2014 в 03:16
  • 3
    Как это может быть отмечено ответом, когда он не отвечает на вопрос и не дает никакого примера использования co / contra variance в c #? – barakcaf 28 June 2016 в 12:02
  • 4
    @barakcaf добавил пример контравариантности. не знаете, почему вы не видели пример ковариации - возможно, вам нужно было прокрутить код вниз, но я добавил некоторые комментарии по этому поводу. – tvanfosson 28 June 2016 в 13:53
  • 5
    @tvanfosson код использует co / contra, я думаю, что он не показывает, как объявить его. В примере не показано использование in / out в общем объявлении, в то время как другой ответ. – barakcaf 28 June 2016 в 13:58

Вот что я собрал, чтобы помочь понять разницу

public interface ICovariant<out T> { }
public interface IContravariant<in T> { }

public class Covariant<T> : ICovariant<T> { }
public class Contravariant<T> : IContravariant<T> { }

public class Fruit { }
public class Apple : Fruit { }

public class TheInsAndOuts
{
    public void Covariance()
    {
        ICovariant<Fruit> fruit = new Covariant<Fruit>();
        ICovariant<Apple> apple = new Covariant<Apple>();

        Covariant(fruit);
        Covariant(apple); //apple is being upcasted to fruit, without the out keyword this will not compile
    }

    public void Contravariance()
    {
        IContravariant<Fruit> fruit = new Contravariant<Fruit>();
        IContravariant<Apple> apple = new Contravariant<Apple>();

        Contravariant(fruit); //fruit is being downcasted to apple, without the in keyword this will not compile
        Contravariant(apple);
    }

    public void Covariant(ICovariant<Fruit> fruit) { }

    public void Contravariant(IContravariant<Apple> apple) { }
}

tldr

ICovariant<Fruit> apple = new Covariant<Apple>(); //because it's covariant
IContravariant<Apple> fruit = new Contravariant<Fruit>(); //because it's contravariant
78
ответ дан CSharper 18 August 2018 в 02:26
поделиться

Из MSDN

В следующем примере кода показана поддержка ковариации и контравариантности для групп методов

static object GetObject() { return null; }
static void SetObject(object obj) { }

static string GetString() { return ""; }
static void SetString(string str) { }

static void Test()
{
    // Covariance. A delegate specifies a return type as object, 
    // but you can assign a method that returns a string.
    Func<object> del = GetString;

    // Contravariance. A delegate specifies a parameter type as string, 
    // but you can assign a method that takes an object.
    Action<string> del2 = SetObject;
}
4
ответ дан Kamran Bigdely 18 August 2018 в 02:26
поделиться

Ключи ввода и вывода управляют правилами литья компилятора для интерфейсов и делегатов с общими параметрами:

interface IInvariant<T> {
    // This interface can not be implicitly cast AT ALL
    // Used for non-readonly collections
    IList<T> GetList { get; }
    // Used when T is used as both argument *and* return type
    T Method(T argument);
}//interface

interface ICovariant<out T> {
    // This interface can be implicitly cast to LESS DERIVED (upcasting)
    // Used for readonly collections
    IEnumerable<T> GetList { get; }
    // Used when T is used as return type
    T Method();
}//interface

interface IContravariant<in T> {
    // This interface can be implicitly cast to MORE DERIVED (downcasting)
    // Usually means T is used as argument
    void Method(T argument);
}//interface

class Casting {

    IInvariant<Animal> invariantAnimal;
    ICovariant<Animal> covariantAnimal;
    IContravariant<Animal> contravariantAnimal;

    IInvariant<Fish> invariantFish;
    ICovariant<Fish> covariantFish;
    IContravariant<Fish> contravariantFish;

    public void Go() {

        // NOT ALLOWED invariants do *not* allow implicit casting:
        invariantAnimal = invariantFish; 
        invariantFish = invariantAnimal; // NOT ALLOWED

        // ALLOWED covariants *allow* implicit upcasting:
        covariantAnimal = covariantFish; 
        // NOT ALLOWED covariants do *not* allow implicit downcasting:
        covariantFish = covariantAnimal; 

        // NOT ALLOWED contravariants do *not* allow implicit upcasting:
        contravariantAnimal = contravariantFish; 
        // ALLOWED contravariants *allow* implicit downcasting
        contravariantFish = contravariantAnimal; 

    }//method

}//class

// .NET Framework Examples:
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable { }
public interface IEnumerable<out T> : IEnumerable { }


class Delegates {

    // When T is used as both "in" (argument) and "out" (return value)
    delegate T Invariant<T>(T argument);

    // When T is used as "out" (return value) only
    delegate T Covariant<out T>();

    // When T is used as "in" (argument) only
    delegate void Contravariant<in T>(T argument);

    // Confusing
    delegate T CovariantBoth<out T>(T argument);

    // Confusing
    delegate T ContravariantBoth<in T>(T argument);

    // From .NET Framework:
    public delegate void Action<in T>(T obj);
    public delegate TResult Func<in T, out TResult>(T arg);

}//class
48
ответ дан Kjartan 18 August 2018 в 02:26
поделиться
  • 1
    Это самый ясный ответ, СПАСИБО! – Hannish 12 January 2017 в 13:05
  • 2
    Предполагая, что Fish является подтипом Animal. Отличный ответ. – Raymond 232 10 April 2017 в 14:07
// Contravariance
interface IGobbler<in T> {
    void gobble(T t);
}

// Since a QuadrupedGobbler can gobble any four-footed
// creature, it is OK to treat it as a donkey gobbler.
IGobbler<Donkey> dg = new QuadrupedGobbler();
dg.gobble(MyDonkey());

// Covariance
interface ISpewer<out T> {
    T spew();
}

// A MouseSpewer obviously spews rodents (all mice are
// rodents), so we can treat it as a rodent spewer.
ISpewer<Rodent> rs = new MouseSpewer();
Rodent r = rs.spew();

Для полноты ...

// Invariance
interface IHat<T> {
    void hide(T t);
    T pull();
}

// A RabbitHat…
IHat<Rabbit> rHat = RabbitHat();

// …cannot be treated covariantly as a mammal hat…
IHat<Mammal> mHat = rHat;      // Compiler error
// …because…
mHat.hide(new Dolphin());      // Hide a dolphin in a rabbit hat??

// It also cannot be treated contravariantly as a cottontail hat…
IHat<CottonTail> cHat = rHat;  // Compiler error
// …because…
rHat.hide(new MarshRabbit());
cHat.pull();                   // Pull a marsh rabbit out of a cottontail hat??
109
ответ дан Marcelo Cantos 18 August 2018 в 02:26
поделиться
  • 1
    Мне нравится этот реалистичный пример. На прошлой неделе я просто написал какой-то кошачий код осел, и я был так рад, что теперь у меня есть ковариация. :-) – Eric Lippert 18 April 2010 в 16:26
  • 2
    @EricLippert Оселгобберлер контравариант, а не ковариант. Это все еще здорово. – javadba 2 February 2014 в 07:27
  • 3
    Этот комментарий выше с @javadba, рассказывающим EricLippert, что такое ковариация и контравариантность, - это реалистичный ковариантный пример того, как я рассказывал своей бабушке, как сосать яйца! :п – iAteABug_And_iLiked_it 30 December 2014 в 14:47
  • 4
    Вопрос не задал вопрос, что может сделать контравариантность и ковариация , он спросил , почему вам нужно использовать его . Ваш пример далек от практического, потому что он также не требует. Я могу создать QuadrupedGobbler и рассматривать его как сам (назначить его IGobbler & lt; Quadruped & gt;), и он все еще может сожрать ослов (я могу передать Donkey in к методу Gobble, который требует Quadruped). Контравариантности не требуется. Это здорово, что мы можем рассматривать QuadrupedGobbler как DonkeyGobbler, но зачем нам в этом случае, если QuadrupedGobbler уже может созреть ослов? – wired_in 27 February 2016 в 01:21
  • 5
    @wired_in Потому что, когда вы заботитесь только о ослах, быть более общим может мешать. Например, если у вас есть ферма, которая поставляет ослов, которые будут поглощены, вы можете выразить это как void feed(IGobbler<Donkey> dg). Если вы взяли IGobbler & lt; Quadruped & gt; как параметр вместо этого, вы не смогли бы пройти в дракона, который только ест ослов. – Marcelo Cantos 27 February 2016 в 01:41
class A {}
class B : A {}

public void SomeFunction()
{
    var someListOfB = new List<B>();
    someListOfB.Add(new B());
    someListOfB.Add(new B());
    someListOfB.Add(new B());
    SomeFunctionThatTakesA(someListOfB);
}

public void SomeFunctionThatTakesA(IEnumerable<A> input)
{
    // Before C# 4, you couldn't pass in List<B>:
    // cannot convert from
    // 'System.Collections.Generic.List<ConsoleApplication1.B>' to
    // 'System.Collections.Generic.IEnumerable<ConsoleApplication1.A>'
}

В принципе, всякий раз, когда у вас есть функция, которая принимает перечислимый тип одного типа, вы не можете передавать в Enumerable производного типа без явно его литья.

Просто, чтобы предупредить вас о ловушка хотя:

var ListOfB = new List<B>();
if(ListOfB is IEnumerable<A>)
{
    // In C# 4, this branch will
    // execute...
    Console.Write("It is A");
}
else if (ListOfB is IEnumerable<B>)
{
    // ...but in C# 3 and earlier,
    // this one will execute instead.
    Console.Write("It is B");
}

Это ужасный код в любом случае, но он существует, и изменение поведения на C # 4 может привести к тонким и трудным для поиска ошибкам, если вы используете такую ​​конструкцию.

30
ответ дан Michael Stum 18 August 2018 в 02:26
поделиться
  • 1
    Это помогает мне понять немного больше, спасибо Майкл – Vince Panuccio 18 April 2010 в 14:38
  • 2
    Таким образом, это влияет на коллекции больше всего на свете, потому что в c # 3 вы можете передать более производный тип в метод менее производного типа. – Vince Panuccio 18 April 2010 в 14:50
  • 3
    Да, большое изменение в том, что IEnumerable теперь поддерживает это, тогда как это не было раньше. – Michael Stum♦ 18 April 2010 в 14:51

Вот простой пример использования иерархии наследования.

Учитывая простую иерархию классов:

                        Giraffe
                      /
LifeForm <- Animal <- 
                      \
                        Zebra

В коде:

public abstract class LifeForm  { }
public abstract class Animal : LifeForm { }
public class Giraffe : Animal { }
public class Zebra : Animal { }

Инвариантность ( Общий с параметризованным типом, украшенным ни in, ни out)

По-видимому, такой метод, как этот

public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
    foreach (var lifeForm in lifeForms)
    {
        Console.WriteLine(lifeForm.GetType().ToString());
    }
}

..., должен принимать гетерогенный набор: (который это делает)

var myAnimals = new List<LifeForm>
{
    new Giraffe(),
    new Zebra()
};
PrintLifeForms(myAnimals); // Giraffe, Zebra

Однако передача набора из более производного типа не выполняется!

var myGiraffes = new List<Giraffe>
{
    new Giraffe(), // "Jerry"
    new Giraffe() // "Melman"
};
PrintLifeForms(myGiraffes); // Compile Error!

cannot convert from 'System.Collections.Generic.List<Giraffe>' to 'System.Collections.Generic.IList<LifeForm>'

Почему? Поскольку общий параметр IList<LifeForm> не является ковариантным - IList<LifeForm> является инвариантным и принимает только коллекции (которые реализуют IList), где параметризованный тип T должен быть LifeForm.

Если я злонамеренно изменяю реализацию метода PrintLifeForms (но оставляю одну и ту же сигнатуру метода), причина, по которой компилятор предотвращает передачу List<Giraffe>, становится очевидным:

 public static void PrintLifeForms(IList<LifeForm> lifeForms)
 {
     lifeForms.Add(new Zebra());
 }

Поскольку IList позволяет добавлять или удалять элементы, любой подкласс LifeForm может быть добавлен к параметру lifeForms и будет нарушать тип любой коллекции производных типов, переданных методу. (В этом случае вредоносный метод попытается добавить Zebra в var myGiraffes). К счастью, компилятор защищает нас от этой опасности.

Ковариация (общий с параметризованным типом, украшенный out)

Ковариация широко используется с неизменяемыми коллекциями (то есть, когда новые элементы не могут быть добавлено или удалено из коллекции)

Решение вышеприведенного примера заключается в том, чтобы использовать ковариантный общий тип, например IEnumerable (определяется как IEnumerable<out T>). Это предотвращает изменение коллекции, и в результате теперь может быть передана любая коллекция с подтипом LifeForm:

public static void PrintLifeForms(IEnumerable<LifeForm> lifeForms)
{
    foreach (var lifeForm in lifeForms)
    {
        Console.WriteLine(lifeForm.GetType().ToString());
    }
}

PrintLifeForms() теперь можно вызвать с помощью Zebras, Giraffes и любой IEnumerable<> любого подкласса LifeForm

Контравариантность (общий с параметризованным типом, украшенным in)

Контравариантность часто используется, когда функции передаются как Параметры.

Вот пример функции, которая принимает параметр Action<Zebra> в качестве параметра и вызывает его на известном экземпляре Zebra:

public void PerformZebraAction(Action<Zebra> zebraAction)
{
    var zebra = new Zebra();
    zebraAction(zebra);
}

Как и ожидалось, это прекрасно работает:

var myAction = new Action<Zebra>(z => Console.WriteLine("I'm a zebra"));
PerformZebraAction(myAction); // I'm a zebra

Интуитивно это не удастся:

var myAction = new Action<Giraffe>(g => Console.WriteLine("I'm a giraffe"));
PerformZebraAction(myAction); 

cannot convert from 'System.Action<Giraffe>' to 'System.Action<Zebra>'

Однако, это преуспевает

var myAction = new Action<Animal>(a => Console.WriteLine("I'm an animal"));
PerformZebraAction(myAction); // I'm an animal

, и даже это также удается:

var myAction = new Action<object>(a => Console.WriteLine("I'm an amoeba"));
PerformZebraAction(myAction); // I'm an amoeba

Почему? Поскольку Action определяется как Action<in T>, то есть contravariant.

Хотя это может быть сначала неинтуитивным (например, как можно передать Action<object> как параметр, требующий Action<Zebra> ]?), если вы распакуете этапы, вы заметите, что сама вызываемая функция (PerformZebraAction) отвечает за передачу данных (в данном случае экземпляра Zebra) в функцию - данные не поступают из вызывающий код.

Из-за инвертированного подхода к использованию функций более высокого порядка таким образом, к тому времени, когда вызывается Action, это более производный экземпляр объекта, который вызывается против функции zebraAction (передается как параметр), который сам использует менее производный тип.

10
ответ дан StuartLC 18 August 2018 в 02:26
поделиться

Делегатор конвертера помогает мне визуализировать обе совместно работающие концепции:

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

TOutput представляет ковариацию, в которой метод возвращает более конкретный тип .

TInput представляет собой контравариантность, когда метод передается менее конкретным типом .

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();
1
ответ дан woggles 18 August 2018 в 02:26
поделиться
0
ответ дан Mooncat 6 September 2018 в 17:31
поделиться
0
ответ дан Mooncat 29 October 2018 в 23:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: