Вопрос о неявных преобразованиях в спецификации языка С#

Раздел 6.1 Неявные преобразования определяет преобразование идентичности таким образом:

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

Теперь, какова цель таких предложений?

(В §6.1.6 Неявные ссылочные преобразования)

Неявные ссылочные преобразования:

  • [...]
  • От любого ссылочного типа до ссылочного типа T, если он имеет неявное преобразование идентичности или ссылки в ссылочный тип T 0 и T 0 имеет преобразование идентичности в T.

и

(В §6.1.7 Бокс конверсии)

  • Тип значения имеет преобразование бокса в тип интерфейса I, если он имеет преобразование бокса в тип интерфейса I 0 и I 0 имеет преобразование идентичности в I.

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

Можете ли вы привести пример двух типов T 1, T 2, чтобы T 1 не будет неявно конвертируемым в T 2, если он не будет использоваться для вышеприведенных абзацев?

Ответ 1

В разделе 4.7 спецификации указано, что существует преобразование идентификатора от Foo<dynamic> до Foo<object> и наоборот. Часть указанной вами спецификации записывается для обеспечения того, чтобы этот случай обрабатывался. То есть, если есть неявное преобразование ссылки из T в C<object, object>, то также имеется неявное ссылочное преобразование в C<object, dynamic>, C<dynamic, object> и C<dynamic, dynamic>.

Можно разумно указать, что (1) намерение этих фраз является неочевидным - следовательно, ваш вопрос - и запутанный, и (2), что раздел о конверсиях идентичности должен перекрестно ссылаться на раздел о динамических преобразованиях и ( 3) такие фразы, как это в спецификации, затрудняют для переводчика спецификацию переводчика в реализацию. Как узнать, существует ли такой тип? Спецификация не должна указывать точные алгоритмы, но было бы неплохо, если бы она давала больше указаний.

Спецификация, к сожалению, не идеальный документ.

Ответ 2

Обновление от 22 сентября 2010 года:

Я сомневаюсь, что кто-нибудь прочтет это, кроме Тимви. Тем не менее, я хотел внести несколько изменений в этот ответ в свете того факта, что новый ответ уже принят, и дебаты по-прежнему продолжаются (по крайней мере, в моем, возможно, мнимом мире) о том, цитируются ли цитируемые выдержки из спецификации технически излишни. Я не добавляю много, но он слишком существенный, чтобы вписаться в комментарий. Основная часть обновления находится в разделе "Конверсия с типом dynamic" .


Обновление от 19 сентября 2010 года:

В Вашем комментарии:

[T] его не имеет смысла.

Черт, Тимви, ты так много говоришь. Но все в порядке; ты поставил меня на оборону, вот и все!

Отказ от ответственности: у меня определенно не проверено спецификацию так близко, как у вас есть. Основываясь на некоторых из ваших последних вопросов, похоже, что вы изучали его совсем недавно. Это, естественно, сделает вас более знакомыми с множеством деталей, чем большинство пользователей на SO; поэтому это, как и большинство ответов, которые вы, вероятно, получите от кого-либо, кроме Эрика Липперта, может не удовлетворить вас.

Различные помещения

Во-первых, предпосылка вашего вопроса заключается в том, что если выделенные выражения излишни, то они не имеют никакой цели. Моя посылка заключается в том, что избыточные заявления не обязательно без цели, если они проясняют то, что не очевидно для всех. Это противоречивые предпосылки. И если мы не можем договориться о помещениях, мы не можем иметь простой логический аргумент. Я просто попросил вас переосмыслить ваше предположение.

Ваш ответ, однако, должен был повторить ваше предположение: "Если предложения действительно излишни, то они только путают читателя и ничего не разъясняют".

(Мне нравится, как вы, кстати, настраиваете себя как представитель для всех читателей спектакля).

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

Цель определения преобразования идентификатора

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

Это позволяет делать заявления о конверсиях, которые по существу транзитивны по своей природе. Первая точка, которую вы указали из спецификации в качестве примера тавтологического утверждения, относится к этой категории. В нем говорится, что если неявное преобразование определено для некоторого типа (я буду называть его K) другим типом T 0, а T 0 имеет преобразование идентичности в T, то K неявно преобразуется в T. По определению преобразования идентичности, приведенному выше, "имеет преобразование идентичности в" действительно означает "- это тот же тип, что и". Таким образом, утверждение является излишним.

Но опять же: определение преобразования идентичности существует, в первую очередь, для оснащения спецификации формальным языком для описания преобразований без необходимости говорить такие вещи, как "если T 0 и T действительно одинаковы."

ОК, время для конкретных примеров.

Если существование неявного преобразования может быть не очевидным для некоторых разработчиков

Примечание. Пример гораздо лучше предоставлен Эриком Липпертом в его ответе на вопрос. Я оставляю эти первые два примера как лишь незначительные подкрепления моей точки зрения. Я также добавил третий пример, который конкретизирует преобразование идентичности, существующее между object и dynamic, как указано в ответе Эрика.

Преобразование с обратной связью

Скажем, у вас есть два типа: M и N, и у вас есть неявное преобразование, определенное следующим образом:

public static implicit operator M(N n);

Затем вы можете написать код следующим образом:

N n = new N();
M m = n;

Теперь скажем, что у вас есть файл с этим выражением using вверху:

using K = M;

А потом у вас есть, позже в файле:

N n = new N();
K k = n;

ОК, прежде чем продолжить, я понимаю, что это очевидно для you и me, Но мой ответ и был с самого начала, что это может быть не очевидно всем, и поэтому указание его - в то время как избыточное - все еще имеет цель.

Эта цель заключается в том, чтобы дать понять кому-либо, царапающему его или ее голову, глядя на этот код, это законно. Существует неявное преобразование от N до M, и существует преобразование идентичности от M до K (т.е. M и K являются одним и тем же типом); поэтому существует неявное преобразование от N до K. Это не просто логично (хотя это может быть логично); он находится прямо в спецификации. В противном случае можно ошибочно полагать, что необходимо что-то вроде следующего:

K k = (M)n;

Очевидно, что это не так.

Конверсионное преобразование бокса

Или возьмите тип int. int может быть помещен как IComparable<int>, правильно? Итак, это законно:

int i = 10;
IComparable<int> x = i;

Теперь рассмотрим следующее:

int i = 10;
IComparable<System.Int32> x = i;

Опять же, да, это может быть очевидно для вас, я и 90% всех разработчиков, которые могут когда-либо столкнуться с этим. Но для этого небольшого меньшинства, который не видит его сразу: конверсия бокса существует от int до IComparable<int>, а преобразование идентичности существует от IComparable<int> до IComparable<System.Int32> (т.е. IComparable<int> и IComparable<System.Int32> однотипные); поэтому конверсия бокса существует от int до IComparable<System.Int32>.

Преобразование с использованием типа dynamic

Я собираюсь заимствовать из приведенного выше ссылочного примера преобразования и немного подстроить его, чтобы проиллюстрировать отношение идентичности между object и dynamic в версии 4.0 спецификации.

Скажем, мы имеем типы M<T> и N и определили где-то следующее неявное преобразование:

public static implicit operator M<object>(N n);

Тогда допустимо следующее:

N n = new N();
M<dynamic> m = n;

Ясно, что вышеизложенное гораздо менее очевидно, чем два предыдущих примера. Но здесь вопрос в миллион долларов: было бы все еще законным, даже если выдержки из спецификации, указанной в вопросе, не существовали? (я собираюсь назвать эти выдержки Q для краткости.) Если ответ да, то Q на самом деле лишний. Если нет, то это не так.

Я считаю, что ответ да.

Рассмотрим определение преобразования идентичности, определенное в разделе 6.1.1 (здесь я включаю весь раздел здесь, поскольку он довольно короткий):

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

Поскольку object и dynamic считаются эквивалентными, происходит преобразование идентичности между object и dynamic, и между построенными типами, которые являются одинаковыми при замене всех вхождений dynamic на object. [акцент мой]

(Эта последняя часть также включена в раздел 4.7, который определяет тип dynamic.)

Теперь посмотрим на код еще раз. В частности, меня интересует эта одна строка:

M<dynamic> m = n;

Законность этого утверждения (без учета Q) - проблема, обсуждаемая - гипотетическая законность вышеприведенного утверждения, если Q не существует), так как M<T> и N являются настраиваемыми типами, зависит от существования пользовательского неявного преобразования между N и M<dynamic>.

Существует неявное преобразование от N до M<object>. В разделе спецификации, приведенной выше, происходит преобразование идентичности между M<object> и M<dynamic>. По определению преобразования идентичности M<object> и M<dynamic> являются одним и тем же типом.

Итак, как и в первых двух (более очевидных) примерах, я полагаю, что верно, что неявное преобразование существует от N до M<dynamic> даже без учета Q, просто поскольку в первом примере существует неявное преобразование от N до K и что во втором примере существует преобразование бокса от int до IComparable<System.Int32>.

Без Q это гораздо менее очевидно (следовательно, Q); но это не делает его ложным (т.е. Q не требуется для определения этого поведения). Это просто делает его менее очевидным.

Заключение

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

Можете ли вы привести пример двух типов T 1, T 2, так что T 1 не будет неявно конвертируемым в T 2, если это не указано в приведенных выше параграфах?

Никто не собирается встретить этот вызов, Тимви, потому что это невозможно. Возьмите первый отрывок о ссылках. Говорят, что тип K неявно конвертируется в тип T, если он неявно конвертируется в T 0, а T 0 совпадает с T. Разложите это, поставьте его назад, и вы остаетесь с очевидной тавтологией: K неявно конвертируется в T, если он неявно конвертируется в T. Вносит ли это какие-либо новые неявные преобразования? Конечно, нет.

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

Избыточные? Да. Тавтология? Да. Бессмысленно? По-моему, нет.

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


Оригинальный ответ

Я думаю, что вы (возможно, намеренно) игнорируете самый очевидный ответ здесь.

Рассмотрите эти два предложения в своем вопросе:

(1) Первоначально они кажутся излишними (Тавтологичность). (2) Но они должны быть там для какой-то цели, почему они там?

Для меня следствием этих двух предложений является то, что тавтологическое утверждение не имеет никакой цели. Но только потому, что утверждение логически вытекает из установленных предпосылок, что не делает его очевидным для всех. Другими словами, даже если значение (1) истинно, ответ на (2) может быть простым: сделать описанное поведение понятным для любого, кто читает спецификацию.

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

Если бы это было обычной практикой, я думаю, вы найдете много литературы там - не только спецификации, но и научные статьи, статьи, учебники и т.д. - было бы намного короче, плотнее и более трудно понять.

Итак: да, возможно, они избыточны. Но это не отрицает их цели.

Ответ 3

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

Это говорит о том, что в С# -land, 1 == 1; Лопата - это Спейд. Это является основой для назначения ссылки объекта на переменную того же типа; если переменная, напечатанная T1 и одна Tped T2, на самом деле являются как Spades, ее можно назначить другому, не указывая явно, как Spade. Представьте себе вариант С#, где назначения должны были выглядеть так:

Spade mySpade = new Spade();
Spade mySpade2;

mySpade2 = (Spade)mySpade; //explicit cast required

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

public int myMethod() { /*Do something*/ }
...
int myInt = (int)myMethod(); //required even though myMethod() evals to an int.
...
int myInt = (int)(1 + 2); //required even though 1, 2, and 1+2 eval to an int.

Второе правило в основном говорит о том, что тип значения может быть назначен переменной-члену в классе, если, в частности, переменная-член (по определению в виде бокса по типу, так как ее контейнер является ссылочным типом) объявляется как такой же тип. Если бы это правило не было определено, теоретически может существовать версия С#, в которой чистые типы значений должны быть явно преобразованы в их ссылочный аналог, чтобы быть сохраненными как переменная-член в классе. Представьте себе, например, версию С#, в которой синие ключевые слова (int, float, decimal) и имена светло-голубых классов (Int32, Float, Decimal) относятся к двум очень разным типам с явно выраженной конвертируемой структурой и int, float, decimal и т.д. не были легальными в качестве типов переменных-членов, поскольку они не были ссылочными типами:

public class MyClass
{
  Int32 MyBoxedValueType; //using "int" not legal
}

...

MyClass myClass = new MyClass();
int theInt = 2;

myClass.MyBoxedValueType = (Int32)theInt; //explicit cast required

Я знаю, это звучит глупо, но на каком-то уровне эти вещи должны быть известны, а на компьютерах вы должны указать ВСЕ. Прочитайте свод правил США по хоккею для хоккея с шайбой; самое первое правило в книге состоит в том, что игра должна воспроизводиться на поверхности льда. Это один из лучших "хороших духов", но также и фундаментальная правда игры, которую нужно понимать, чтобы любое другое правило имело смысл.

Ответ 4

Может быть, такой код гарантирует прохождение при вызове как Convert.ChangeType(client, typeof(Client)) независимо от того, выполняется ли IConvertible.

Посмотрите на источник ChangeType из mscorlib с помощью Reflector и обратите внимание на условия, при которых value возвращается как есть.

Помните, что оператор = не является преобразованием, а просто набором ссылок. Таким образом, код типа Client client_2 = client_1 не выполняет никаких неявных преобразований. Если объявлено неявное преобразование, то выдается ошибка CS0555.

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