Почему «динамический» не является ковариантным и контравариантным по отношению ко всем типам при использовании в качестве параметра универсального типа? - PullRequest
22 голосов
/ 04 февраля 2011

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

Я написал небольшой эксперимент на C # 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 отличается между присваиванием и ковариацией / контравариантностью с генериками?»

Ответы [ 4 ]

23 голосов
/ 04 февраля 2011

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

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

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

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

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

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

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

Frob x = (Frob)whatever;

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

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

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

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

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

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

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

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

2 голосов
/ 04 февраля 2011

для этого:

ICovariance<string> c7 = new Covariance<dynamic>();

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

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

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

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

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

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

1 голос
/ 04 февраля 2011

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

Я думаю, вы как бы ответили на свой вопрос.Безопасность типа присваивания смягчается в операторах присваивания, потому что именно так динамично работает;он замыкает проверку типов во время компиляции, так что вы можете выполнять назначения, которые вы ОЖИДАЕТЕ для работы с объектами, о которых компилятор понятия не имеет.

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

0 голосов
/ 04 февраля 2011

"Почему семантика динамического различия между присваиванием и ковариацией / контравариантностью с обобщениями?"

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...