Подтвердить что ты не робот

Почему "динамический" не ковариантный и контравариантный по отношению ко всем типам при использовании в качестве типового параметра типа?

Мне интересно, если dynamic является семантически эквивалентным object при использовании в качестве параметра типового типа. Если это так, мне любопытно, почему это ограничение существует, поскольку они различаются при назначении значений переменным или формальным параметрам.

Я написал небольшой эксперимент в С# 4.0, чтобы разделить некоторые детали. Я определил некоторые простые интерфейсы и реализации:

interface ICovariance<out T> { T Method(); }

interface IContravariance<in T> { void Method(T argument); }

class Covariance<T> : ICovariance<T>
{
    public T Method() { return default(T); }
}

class Contravariance<T> : IContravariance<T>
{
    public void Method(T argument) { }
}

Интересные подробности эксперимента:

class Variance
{
    static void Example()
    {
        ICovariance<object> c1 = new Covariance<string>();
        IContravariance<string> c2 = new Contravariance<object>();

        ICovariance<dynamic> c3 = new Covariance<string>();
        IContravariance<string> c4 = new Contravariance<dynamic>();

        ICovariance<object> c5 = new Covariance<dynamic>();
        IContravariance<dynamic> c6 = new Contravariance<object>();

        // The following statements do not compile.
        //ICovariance<string> c7 = new Covariance<dynamic>();
        //IContravariance<dynamic> c8 = new Contravariance<string>();

        // However, these do.
        string s = new Covariance<dynamic>().Method();
        new Contravariance<string>().Method((dynamic)s);       
    }
}

Первые два оператора с c1 и c2 демонстрируют, что основная ковариация и контравариантность работают. Затем я использую c3 и c4, чтобы показать, что dynamic может использоваться как общий тип типа таким же образом.

Операторы с c5 и c6 показывают, что всегда выполняется преобразование с dynamic в object. Это не слишком удивительно, так как object является предком всех других типов.

Последний эксперимент с c7 и c8 заключается в том, что я начинаю запутываться. Это означает, что методы, возвращающие объекты dynamic, не заменяют методы, возвращающие теги string, и аналогично, что методы, которые принимают объекты string, не могут принимать теги dynamic. Последние два утверждения с вызовом присваивания и метода показывают, что это явно не так, поэтому моя путаница.

Я немного подумал об этом и задался вопросом, не препятствует ли программистам использовать ICovariance<dynamic> в качестве шага между преобразованиями типов, что приведет к ошибкам во время выполнения, например:

ICovariance<dynamic> c9 = new Covariance<Exception>();
ICovariance<string> c10 = c9;
// While this is definitely not allowed:
ICovariance<string> c11 = new Covariance<Exception>();

Однако это неубедительно в случае dynamic, поскольку мы все равно теряем безопасность типов:

dynamic v1 = new Exception();
string  v2 = v1;

Иными словами, вопрос "Почему семантика dynamic отличается от назначения и ковариации/контравариантности с дженериками?"

4b9b3361

Ответ 1

Мне интересно, если динамический семантически эквивалентен объекту при использовании в качестве параметра типового типа.

Ваша гипотеза полностью верна.

"динамический" как тип - не что иное, как "объект" со смешной шляпой, шляпа, которая говорит "вместо того, чтобы делать статическую проверку типа для этого выражения объекта типа, сгенерировать код, который выполняет проверку типа во время выполнения", Во всех остальных отношениях динамика - это просто объект, конец истории.

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

Подумайте об этом с точки зрения компилятора, а затем с точки зрения верификатора IL.

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

То есть, компилятор генерирует:

Frob x = (Frob)whatever;

Но ограничивает конверсии неявными преобразованиями, а не явные преобразования.

Когда значение является динамическим, компилятор в основном говорит: "Мне нужно сгенерировать код, который запрашивает этот объект во время выполнения, определяет его тип, снова запускает компилятор и выплескивает небольшой фрагмент IL, который преобразует все, что этот объект к типу этой переменной, запускает этот код и присваивает результат этой переменной. И если какой-либо из них терпит неудачу, бросьте."

То есть, компилятор генерирует моральный эквивалент:

Frob x = MakeMeAConversionFunctionAtRuntime<Frob>((object)whatever);

Верификатор даже не мигает. Верификатор видит метод, который возвращает Frob. Этот метод может вызвать исключение, если он не может превратить "все" в Frob; в любом случае, только Frob всегда записывается в x.

Теперь подумайте о своей ковариационной ситуации. С точки зрения CLR, нет такой вещи, как "динамический". Всюду, где у вас есть аргумент типа, который является "динамическим", компилятор просто генерирует "объект" как аргумент типа. "dynamic" - это функция языка С#, а не функция Common Language Runtime. Если ковариация или контравариантность на "объекте" не является законной, то она также не является законной по "динамической". Там нет IL, который компилятор может генерировать, чтобы система CLR-типа работала по-другому.

Это объясняет, почему вы наблюдаете, что есть преобразование, скажем, List<dynamic> в и из List<object>; компилятор знает, что они одного типа. В спецификации фактически указывается, что эти два типа имеют преобразование идентичности между ними; они идентичны.

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

Ответ 2

для этого:

ICovariance<string> c7 = new Covariance<dynamic>();
Причина

очевидна, если это было возможно, вы могли бы сделать:

c7.Method().IndexOf(...);

и он определенно завершится неудачей, кроме случаев, когда dynamic не является string или имеет этот метод.

так как (даже после всех изменений) С# не является динамическим языком. Ковариация разрешена только тогда, когда она определенно безопасна. Вы можете, конечно, влезть в ваши ноги и вызвать IndexOf в переменной dynamic, но вы не можете позволить пользователям вашего API делать это непреднамеренно. Например, если вы вернете такой код ICovariance<string> с dynamic, вызывающий код под прикрытием может завершиться неудачно!

Помните правило, D ковариантно к B, если есть от D до B. В этом случае отбрасывается от dynamic до string.

Но dynamic ковариантно к object только потому, что все выведено из него.

Ответ 3

Потому что динамические и ковариантные/контравариантные ключевые слова настолько новы?

Я бы предположил, что вы ответили на свой вопрос. Тип присваивания - безопасность ослабляется в операторах присваивания, поскольку это работает динамично; это короткое замыкание на проверку типа компиляции, чтобы вы могли назначать команды EXPECT для работы с объектами, о компилере которых не существует.

Однако общая ковариация/контравариантность жестко контролируется; без использования ключевых слов in/out (которые также были введены наряду с динамическими в С# 4.0), вы не могли бы преобразовать оба способа. Общие параметры, даже если допускается совпадение/контравариантность, требуют, чтобы типы находились в одной и той же ветки иерархии наследования. Строка не является динамической, а динамика не является строкой (хотя оба являются объектами, а динамические могут ссылаться на то, к чему можно получить в виде строки), поэтому общая проверка типов, присущая проверке ковариации/контравариантности, терпит неудачу, тогда как OTOH, компилятору явно предлагается игнорировать большинство неоригинальных операций, связанных с динамическими.

Ответ 4

"Почему семантика динамических отличается от назначения и ковариации/контравариантности с дженериками?"

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

Итак, если у вас есть "ICovariance c9 = new Covariance();" как динамические, так и исключения не имеют одинаковых функциональных возможностей (как базовые типы). Более того, компилятор не имеет понятия о том, как преобразовать из динамического в исключение (хотя оба они наследуют объект).

Если существовала явная иерархия наследования между dynamic и Exception (кроме объекта), то это было бы несколько нормально.

Причина в том, что вы можете подавлять, но не повышать. EG, если исключение наследуется от динамического, то это будет хорошо. Если динамический наследует от Exception, это будет сложная сделка, и это не будет нормально, поскольку может существовать условие, в котором "динамическое data is not present in исключение".

У .NET есть эти явные приемы типов, и вы можете видеть их в действии в объекте System.Convert. Тем не менее, типы, которые являются суперспецифичными, не могут быть легко неявно или явно лишены между собой без специального кода. И это одна из причин отказа нескольких типов (как в случае с "ICovariance c9 = new Covariance();`)). Это также создано для сохранения безопасности типов.