Совместная / контравариантная поддержка неуниверсальных типов? [Дубликат]

2

Интересно, почему команда С# решила не поддерживать совместную/контравариантность для не-дженериков, учитывая, что они могут быть столь же безопасными. Вопрос довольно субъективный, так как я не ожидаю ответа члена команды, но у кого-то может быть понимание я (и Барбары Лисков).

Давайте посмотрим на этот пример интерфейса:

public interface ITest
{
    object Property
    {
        get;
    }
}

Следующая реализация не удастся, хотя и полностью безопасна (мы всегда можем вернуть более конкретный тип без нарушения интерфейса - не в С#, но, по крайней мере, в теории).

public class Test : ITest
{
    public string Property
    {
        get;
    }
}

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

  • 0
    Естественно, эта реализация также потерпит неудачу, потому что get не имеет тела, но для этого простого примера, пожалуйста, игнорируйте этот факт ;-)
  • 1
    Дуп: stackoverflow.com/questions/837134/…
Показать ещё 2 комментария
Теги:
covariance
contravariance

3 ответа

2

CLR не поддерживает ковариантные типы возвращаемых данных, тогда как он поддерживает общую дисперсию делегирования/интерфейса с .NET 2.0 и далее.

Другими словами, это действительно не команда С#, а команда CLR.

Что касается того, почему среда CLR не поддерживает нормальную дисперсию - я не уверен, кроме добавления сложности, предположительно, без необходимого количества предполагаемой выгоды.

РЕДАКТИРОВАТЬ: Чтобы противостоять точке ковариации возвращаемого типа, из раздела 8.10.4 спецификации CLI, говорим о "слотах" vtable:

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

Из раздела II, раздел 9.9:

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

Нет никаких указаний на то, что сравнение выполняется таким образом, чтобы это допускало отклонение.

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

EDIT: Я только что попробовал это в IL, и это не сработает. Скомпилируйте этот код:

using System;

public class Base
{
    public virtual object Foo()
    {
        Console.WriteLine("Base.Foo");
        return null;
    }
}

public class Derived : Base
{
    public override object Foo()
    {
        Console.WriteLine("Derived.Foo");
        return null;
    }
}

class Test
{
    static void Main()
    {
        Base b = new Derived();
        b.Foo();
    }
}

Запустите его, с выходом:

Derived.Foo

Разберите его:

ildasm Test.exe /out:Test.il

Измените Derived.Foo, чтобы иметь тип возврата "string" вместо "object":

.method public hidebysig virtual instance string Foo() cil managed

Rebuild:

ilasm /OUTPUT:Test.exe Test.il

Перезапустите его, с выходом:

Base.Foo

Другими словами, Derived.Foo больше не переопределяет Base.Foo в отношении CLR.

  • 0
    «Сигнатура метода определяет соглашение о вызове, тип параметров метода и тип возврата метода» (из ECMA-335, 8.11.1). Поскольку CLR является реализацией CLI, не будет ли он поддерживать перегрузку возвращаемого типа - и, таким образом, разрешать дисперсию?
  • 0
    Нет, потому что поддержка перегрузки возвращаемого типа - это не то же самое, что поддержка дисперсии. Я посмотрю соответствующий бит на переопределение, которое является важной частью.
Показать ещё 2 комментария
1

CLR не поддерживает дисперсию над переопределениями метода, но для реализации интерфейса существует обходное решение:

public class Test : ITest
{
    public string Property
    {
        get;
    }

    object ITest.Property
    {
        get
        {
            return Property;
        }
    }
}

Это приведет к такому же эффекту, что и ковариантное переопределение, но может использоваться только для интерфейсов и для прямых реализаций

  • 0
    Вот так я и решил эту проблему, но поскольку все больше классов используют похожий подход, код становится уродливым. Я думаю, у меня нет выбора, хотя.
1
  • Возвращаемое значение метода всегда "вне", они всегда находятся на правой стороне выражения присваивания.
  • Формат сборки CLR имеет метаданные, которые присваивают методы экземпляра методам интерфейса, но этот вывод метода зависит только от входных параметров, им может потребоваться создать новый формат для поддержки, а не только в том, что он также становится неоднозначным.
  • Новый алгоритм сопоставления методов между методом экземпляра и сигнатурой интерфейса может быть сложным и интенсивным. И я верю, что подписи методов интерфейса решаются во время компиляции. Потому что во время выполнения это может быть слишком дорого.
  • Вывод/разрешение метода может быть проблемой, как описано ниже.

Рассмотрим следующий образец с разрешенным разрешением динамического метода только с разными типами возвращаемых значений (что абсолютно неверно)

public class Test{
     public int DoMethod(){ return 2; }
     public string DoMethod() { return "Name"; }
} 
Test t;
int n = t.DoMethod();  // 1st method
string txt = t.DoMethod(); // 2nd method
object x = t.DoMethod(); // DOOMED ... which one??
  • 0
    Эта проблема существует уже сегодня, если у вас есть класс, который реализует два интерфейса и передает экземпляр методу, который имеет перегрузки для обоих интерфейсов.
  • 0
    Как я прокомментировал Джону Скиту, в CTS сигнатура метода включает тип возвращаемого значения.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню