C # ковариация путаницы - PullRequest
0 голосов
/ 18 января 2019

Ниже приведен фрагмент кода о ковариации в C #. У меня есть некоторое понимание того, как применить ковариацию, но есть некоторые подробные технические вещи, которые мне трудно понять.

using System;
namespace CovarianceExample
{
    interface IExtract<out T> { T Extract(); }
    class SampleClass<T> : IExtract<T>
    {
        private T data;
        public SampleClass(T data) {this.data = data;}   //ctor
        public T Extract()                               // Implementing interface
        {
            Console.WriteLine
                ("The type where the executing method is declared:\n{0}",
                this.GetType() );
            return this.data;
        }
    }
    class CovarianceExampleProgram
    {
        static void Main(string[] args)
        {
            SampleClass<string> sampleClassOfString = new SampleClass<string>("This is a string");
            IExtract<Object> iExtract = sampleClassOfString;

            // IExtract<object>.Extract() mapes to IExtract<string>.Extract()?
            object obj = iExtract.Extract();
            Console.WriteLine(obj);                  
            Console.ReadKey();
        }
    }
}

// Output:
// The type where the executing method is declared:
// CovarianceExample.SampleClass`1[System.String]
// This is a string

Invoking IExtract<object>.Extract() вызывает IExtract<string>.Extract(), что подтверждается выводом. Хотя я ожидал такого поведения, я не могу сказать себе, почему он вел себя так, как он.

IExtract<object> - это NOT в иерархии наследования, содержащей IExtract<string>, за исключением того факта, что C # сделал IExtract<string> присваиваемым IExtract<object>. Но IExtract<string> просто НЕ имеет метод с именем Extract(), который он наследует от IExtract<object>, в отличие от нормального наследования. В настоящее время это не имеет особого смысла для меня.

Разумно ли было бы сказать, что IExtract<string> OWN по совпадению (или по замыслу) с аналогичным именем Extract() метод скрывает IExtract<object> метод Extract()? И что это за взлом? (Плохой выбор слова!)

Спасибо

1 Ответ

0 голосов
/ 18 января 2019

У вас определенно есть серьезные недоразумения о том, как работает ковариация, но мне не на 100% ясно, что это такое. Позвольте мне сначала сказать, что такое интерфейсы, а затем мы можем построчно рассмотреть ваш вопрос и указать на все недоразумения.

Думайте об интерфейсе как о наборе «слотов», где каждый слот имеет контракт , а содержит метод, который выполняет этот контракт. Например, если у нас есть:

interface IFoo { Mammal X(Mammal y); }

тогда IFoo имеет одну ячейку, и эта ячейка должна содержать метод, который берет млекопитающее и возвращает млекопитающее.

Когда мы неявным или явным образом преобразуем ссылку в тип интерфейса, мы никоим образом не меняем ссылку . Скорее, мы проверяем , что указанный тип уже имеет действительную таблицу слотов для этого интерфейса. Так что если у нас есть:

class C : IFoo { 
  public Mammal X(Mammal y)
  {
    Console.WriteLine(y.HairColor);
    return new Giraffe();
  }
}

и позже

C c = new C();
IFoo f = c;

Думайте о C как о наличии маленькой таблицы с надписью «если C преобразуется в IFoo, C.X попадает в слот IFoo.X»

Когда мы конвертируем c в f, c и f имеют точно такой же контент . Они одинаковые ссылки . Мы только что подтвердили , что тип c имеет таблицу слотов, совместимую с IFoo.

Теперь давайте пройдемся по вашему сообщению.

Invoking IExtract<object>.Extract() вызывает IExtract<string>.Extract(), что подтверждается выводом.

Давайте разберемся с этим.

У нас есть sampleClassOfString, который реализует IExtract<string>. Его тип имеет «таблицу слотов», которая гласит: «Мой Extract входит в слот для IExtract<string>.Extract».

Теперь, когда sampleClassOfString конвертируется в IExtract<object>, мы снова должны сделать проверку. Содержит ли тип sampleClassOfString таблицу слотов интерфейса, подходящую для IExtract<object>? Да, это так: мы можем использовать существующую таблицу для IExtract<string> для этой цели .

Почему мы можем использовать его, даже если это два разных типа? Потому что все контракты все еще выполняются .

IExtract<object>.Extract имеет контракт: это метод, который ничего не берет и возвращает object. Ну, метод, который находится в слоте IExtract<string>.Extract, соответствует этому контракту; он ничего не берет и возвращает строку, которая является объектом.

Поскольку все контракты выполнены, мы можем использовать таблицу слотов IExtract<string>, которая у нас уже есть. Назначение выполнено успешно, и все вызовы пройдут через таблицу слотов IExtract<string>.

IExtract<object> НЕ находится в иерархии наследования, содержащей IExtract<string>

Правильно.

за исключением того факта, что C # сделал IExtract<string> присваиваемым IExtract<object>.

Не путайте эти две вещи; Они не то же самое. Наследование - это свойство, что член базового типа также является членом производного типа . Совместимость назначений - это свойство, которое экземпляру одного типа может быть присвоено переменной другого типа. Это логически очень разные!

Да, существует связь, поскольку деривация подразумевает как совместимость присваивания, так и наследование ; если D является производным типом базового типа B, то экземпляр D можно назначить переменной типа B, и все наследуемые члены B являются членами D.

Но не путайте эти две вещи; только то, что они связаны, не означает, что они одинаковы. Есть на самом деле языки, где они разные; то есть существуют языки, в которых наследование ортогонально совместимости присваивания. C # просто не один из них, и вы так привыкли к миру, где наследование и совместимость присваиваний так тесно связаны, что вы никогда не учились видеть их как отдельные. Начни думать о них как о разных вещах, потому что они есть.

Ковариация - это расширение отношения совместимости присваивания для типов, которые не находятся в иерархиях наследования. Вот что означает ковариация ; отношение совместимости присваивания равно ковариантному , если отношение сохраняется при сопоставлении с универсальным . «Яблоко можно использовать там, где нужен фрукт, поэтому последовательность яблок можно использовать там, где требуется фрукт» - ковариация . Отношение совместимости назначения сохраняется при отображении в последовательности .

Но IExtract<string> просто НЕ имеет метода с именем Extract(), который он наследует от IExtract<object>

Это верно. Не существует наследования между IExtract<string> и IExtract<object>. Однако между ними существует совместимость , потому что любой метод Extract, который соответствует контракту IExtract<string>.Extract, является также методом, который соответствует контракту IExtract<object>.Extract. Поэтому таблица слотов первого может использоваться в ситуации, требующей последнего.

Разумно ли было бы сказать, что СОБСТВЕННЫЙ IExtract<string>, по совпадению (или по замыслу) с аналогичным названием Extract() метод скрывает IExtract<object> метод Extract ()?

Абсолютно нет. Там нет никакого сокрытия вообще. «Скрытие» происходит, когда производный тип имеет элемент с тем же именем, что и унаследованный элемент базового типа, а новый элемент скрывает старый для поиска имен во время компиляции . Сокрытие является исключительно концепцией поиска имени во время компиляции; он не имеет никакого отношения к тому, как интерфейсы работают во время выполнения.

А что это за хак?

АБСОЛЮТНО НЕ .

Я пытаюсь не находить предложение оскорбительным, и в основном это удается. : -)

Эта функция была тщательно разработана экспертами; это звук (по модулю распространяющийся на существующие несоответствия в C #, такие как небезопасная ковариация массивов), и он был реализован с большой осторожностью и проверкой. В этом нет абсолютно ничего «хакерского».

так что же происходит, когда я вызываю IExtract<object>.Extract()?

Логически, вот что происходит:

Когда вы преобразуете ссылку на класс в IExtract<object>, мы проверяем, что в ссылке есть таблица слотов, совместимая с IExtract<object>.

Когда вы вызываете Extract, мы ищем содержимое слота Extract в таблице слотов, которую мы определили как совместимую с IExtract<object>. Поскольку это та же таблица слотов , что и у объекта, уже имеющегося для IExtract<string>, происходит то же самое: метод класса Extract находится в этом слоте, поэтому он вызывается.

На практике ситуация немного сложнее; в логике вызова есть куча механизмов, обеспечивающих хорошую производительность в обычных случаях. Но по логике вы должны думать об этом как о том, чтобы найти метод в таблице, а затем вызвать этот метод.

Делегаты также могут быть помечены как ковариантные и контравариантные. Как это работает?

Логически вы можете рассматривать делегатов как просто интерфейсы, которые имеют единственный метод, называемый Invoke, и это следует из этого. На практике, конечно, механизмы несколько отличаются благодаря таким вещам, как состав делегатов, но, возможно, теперь вы можете увидеть, как они могут работать.

Где я могу узнать больше?

Это что-то вроде пожарного шланга:

https://stackoverflow.com/search?q=user%3A88656+covariance

поэтому я бы начал сверху:

Разница между ковариацией и контрастностью

Если вы хотите историю функции в C # 4.0, начните здесь:

https://blogs.msdn.microsoft.com/ericlippert/2007/10/16/covariance-and-contravariance-in-c-part-one/

Обратите внимание, что это было написано до того, как мы остановились на "in" и "out" в качестве ключевых слов для контравариантности и ковариации.

Здесь можно найти множество других статей в хронологическом порядке "новейшей первой":

https://blogs.msdn.microsoft.com/ericlippert/tag/covariance-and-contravariance/

и несколько здесь:

https://ericlippert.com/category/covariance-and-contravariance/


УПРАЖНЕНИЕ: Теперь, когда вы примерно знаете, как это работает за кулисами, как вы думаете, что это делает?

interface IFrobber<out T> { T Frob(); }
class Animal { }
class Zebra: Animal { }
class Tiger: Animal { }
// Please never do this:
class Weird : IFrobber<Zebra>, IFrobber<Tiger>
{
  Zebra IFrobber<Zebra>.Frob() => new Zebra();
  Tiger IFrobber<Tiger>.Frob() => new Tiger();
}
…
IFrobber<Animal> weird = new Weird();
Console.WriteLine(weird.Frob());

? Подумайте об этом и посмотрите, сможете ли вы решить, что происходит.

...