Почему Char неявно конвертируется в ushort, но не наоборот?

Ещё один хороший вопрос со StackOverflow. Почему есть неявное преобразование из char в ushort, но только явное из ushort в char? Почему дизайнеры языка верят, что эти асимметричные правила имело смысл добавлять в язык?

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

Так что можно позволить оба неявных преобразования. Но, только то, что что-то возможно не означает, что это хорошая идея. Явно дизайнеры языка думали, что неявное преобразование char в ushort - хорошая идея, но неявное преобразование из ushort в char – нет. (А, поскольку char в ushort - хорошая идея, то выглядит разумным, что char во что-угодно-во-что-преобразуется ushort тоже, так что char в int тоже можно.)

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

Преобразовение из ushort в char упоминается в заметках от 14 апреля 1999, где поднимается вопрос, стоит ли разрешать преобразование из байта в char. В исходной пред-релизной версии C #, это было разрешено. Я слегка подредактировал заметки, чтобы сделать их ясными без понимания пред-релизных кодовых названий Майкрософт эпохи 1999. Я также добавил выделение в важных местах:

[Комитет по проектированию языка] выбрал предоставление неявной конверсии из байтов в символы, поскольку область значений первых полностью покрывается вторыми. Тем не менее, прямо сейчас [авторы библиотеки среды исполнения] предоставляют только методы Write, которые принимают charы и intы, что означает, что байты печатаются как символы, поскольку этот метод оказывается наилучшим. Мы можем решить это либо предоставлением дополнительных методов Write, либо устранением неявного преобразования.

Есть аргумент в пользу того, что последнее является правильным действием. В конце концов, байты на самом деле не символы. Да, тут может иметь место полезное отображение байтов в символы, но, фундаментально, 23 не обозначает то же самое, что и символ с ASCII кодом 23, в том же смысле, как байт 23 обозначает то же самое, что и длинное целое 23. Просить [разработчиков библиотеки] предоставить этот дополнительный метод просто из-за особенностей нашей системы типов кажется достаточно слабым.

Заметки затем заканчиваются решением, что byte-в-char должно быть явным преобразованием, и целое-в-диапазоне-char тоже должно преобразовываться явно.

Отметим, что заметки по дизайну языка не указывают, почему ushort-в-char тогда же было сделано тоже явным, но, как видите, применима та же логика. Передавая ushort в метод, перегруженный как M(int) и M(char), вы скорее всего хотите трактовать ushort как число, а не как символ. А ushort не является представлением символа в том же смысле, как представлением числа, так что выглядит разумным сделать это преобразование таким же явным.

Решение преобразовывать char в ushort неявно было принято 17 сентября 1999 года; заметки по дизайну за этот день на эту тему гласят просто «char в ushort тоже разрешённое неявное преобразование”, и это всё. Никаких других проявлений того, что происходило в головах дизайнеров языка в тот день в заметках не засвидетельствовано.

Тем не менее, мы можем сделать обоснованные предположения причин, по которым неявное преобразование char в ushort было рассмотрено как хорошая мысль. Ключевая идея здесь в том, что преобразование из числа в символ является «возможно сомнительным». Оно берёт что-то, что не факт, что планировалось быть символом, и решает трактовать его как символ. Это похоже на тот тип действий, который вы хотели бы явно объявлять при выполнении, а не случайно позволять себе. Но обратное гораздо менее сомнительно. В программировании на C есть давняя традиция трактовать символы как целые числа – для получения их фактических значений, или для выполнения с ними математических операций.

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