Составные операторы присваивания. Часть 2

В прошлый раз мы обсуждали потенциально неочевидное поведение составных операторов присваивания вида «x op= y» в языке C#, а именно:

(1) хотя логически такой оператор разворачивается в «x = x op y», x вычисляется только один раз

(2) для встроенных операторов, в случае необходимости добавляется преобразование типов, таким образом, этот код преобразуется в «x = (T)(x op y)»

(3) для встроенных операторов, если «x = y» некорректно, то «x op= y» также некорректно.

Я рад сообщить, что мы находимся на длинном пути добавления новых возможностей в язык C#. Сейчас мы поддерживаем составной оператор присваивания для сложения, удаления, деления, получения остатка, умножения, сдвига влево, сдвига вправо, битового И, ИЛИ, исключающего ИЛИ, но существует большое количество других операторов. В следующей версии языка C# мы добавим составные операторы присваивания для большинства остальных бинарных (и тернарных!) операторов.

Это требует ослабления некоторых существующих правил; мы это обсудим ниже.

Давайте начнем с простого оператора

Равенство/неравенство

В следующей версии языка C#, x !== y будет означать x = (x != y).

Сейчас, для всех встроенных типов x != y возвращает bool, тогда для всех встроенных типов этот синтаксис работает только если x и y оба типа bool. Это оказался самым простым составным оператором присваивания, поскольку мы его уже реализовали! x !== y для типов bool эквивалентно x ^= y, который у нас уже есть.

Аналогично, x === y является противоположным; это аналогично x = !(x^y). Или это аналогично x = (!x)^y? Оказывается и тому и другому! Здорово, правда!

Меня немного беспокоит, что программисты, знакомые с JScript будут путать значение оператора ===, которое в JScript означает эквивалентность без преобразования типов, а в C# – составной оператор присваивания, с эквивалентностью. Но я надеюсь, что это не будет серьезной проблемой.

Составные операторы присваивания для равенства и неравенства очень простые; а как насчет других операторов сравнения? Можем ли мы создать такие составные операторы?

Сравнение

Если бы в этом мире была справедливость, то x <= y означало бы x = x < y, но, к сожалению, оператор <= уже используется. Аналогично с x >= y. К сожалению, у нас нет подходящего синтаксиса для этих операторов, но как скоро мы увидим, в этом ничего страшного.

x >== y и x <== y означает x = x >= y и x = x <= y, соответственно. Сейчас, для всех встроенных типов, для которых определены эти операторы, <= и >= возвращают bool, однако для типа bool (или типа object) операторы <= или >= не определены. Таким образом, эти операторы полезны для не встроенных типов. Однако мы их будем поддерживать. При создании собственного типа, например, C, который содержит явный или неявный оператор преобразования из типа bool, а также содержит определение оператора <=, вы можете использовать x <== y для выражений x и y типа C. Он разворачивается в x = (x <= y), и, конечно же, x будет вычисляться только один раз.

Условные операторы

Давайте рассмотрим еще один оператор, прежде чем перейдем к ловушкам «булевых» операторов. У нас появляется отличная возможность расширить составные операторы присваивания из бинарных операторов в тернарные. Сейчас, C# поддерживает только один тернарный оператор (тернарный оператор сравнения), но в общем случае мы можем рассматривать любой «инфиксный» тернарный оператор как два бинарных оператора. Т.е., вместо того, чтобы рассматривать x ? y : z, как тернарный оператор, думайте о нем, как о двух бинарных операторах, ? и :, которые всегда применяются совместно. Как только вы будете думать об этом в данном ключе, тогда мы можем преобразовать его в составной оператор присваивания:

x ?= y : z, означает x = x ? y : z

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

На StackOverflow часто задают вопрос, почему условные операторы не рассматривают преобразованный тип; в данном синтаксисе он рассматривается, поскольку в случае необходимости встраивается преобразование для встроенных типов. Я надеюсь, это немного уменьшит недопонимание пользователей.

Объединения

x ??= y означает x = x ?? y

Этот оператор достаточно простой и весьма полезный. Анализ типов для оператора ?? немного странный (подробности смотри в спецификации), но семантика оператора уже требует, чтобы с обеих сторон оператора были совместимые типы, по крайней мере в вопросе nullability. В целом этот оператор означает следующее: «если левая сторона оператора равна null, замени его на правую сторону, в противном случае используйте значение с левой стороны».

До сих пор все было достаточно просто. Следующие операторы полностью удаляют ограничение, что переменной «y» можно присвоить «x»; причина этого скоро станет ясна.

Сравнение типов

x is= y означает x = x is y

Опять-таки, поскольку оператор «is» возвращает bool, эта конструкция будет работать только с типами bool и object. Например:

object x = "hello";
x is= Exception;

означает x = (x is Exception), таким образом, x будет равен упакованному «false». Аналогично:

x as= Y означает x = x as Y

Этот код оставляет x неизменным, если тип x того же типа, что и Y, и становится равным null в противном случае.

В обоих случаях мы ослабили ограничение на то, чтобы код x = Y был корректным для встроенных операторов, поскольку это условие практически никогда не будет выполняться.

Вызов метода, индексатора или доступ к члену

x ()= y означает x = x(y)

Это нужно обдумать. Очевидно, что для вызова x должен быть делегатом. А этот делегат должен возвращать делегат, который можно присвоить x. Недавно я написал статью о подобных делегатах; в основном такие делегаты лучше всего работают с комбинаторами:

 delegate D D(D d); // D – это делегат, который принимает D и возвращает D.
void M()
{
    D x = q=>q;
    D y = r=>s=>r(s);
    x ()= y; 
    // означает x = x(y), что в данном конкретном случае всего лишь является присваиванием y к x.
}

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

Теперь вы можете спросить о том, насколько это разумно. Т.е. что x() = y означает присвоить значение y переменной x(). Но в языке C# результат вызова метода никогда не является переменной, это всегда либо значение, либо void.

Но это не так в общем случае в CLR; как я упоминал в прошлый раз, система типов поддерживает методы, который возвращают ссылку на переменную. Если мы когда-то захотим добавить метод, возвращающий переменную в язык C#, то сделать это будет не просто. Мы решили не добавлять этот составной оператор присваивания, поскольку это осложнит добавление в будущем методов, возвращающих переменные. Но сегодняшняя отличная функциональность того стоит; мы можем никогда не реализовать методы, возвращающие переменные, так что напрасно лишать людей потрясающей возможности из-за потенциальной возможности в будущем.

Аналогично вызову мы можем выполнять индексирование. x []= y означает x = x[y]. Типичным вариантом использования этой возможности является случай, когда x относится к типу, содержащему индексатор, который, в свою очередь, возвращает экземпляр этого типа.

Немного странным случаем, который вызывал бурные дискуссии на наших митингах, является обращение к члену. x .= y означает x = x.Y. В этом случае Y явно должен быть полем или свойством типа, совместимого с x, или группой методов ( method group), а x – типом делегата, совместимого с ними. (В этом случае Y ограничен именами методов или типами делегатов, хорошим примером является метод Invoke.)

Не поддерживаемые возможности

Мы не будем поддерживать new, анонимные методы и лямбда-выражения. После длительных обсуждений мы решили не поддерживать x new= Y, x delegate= {y} или мой любимый вариант, x =>= y. Обратите внимание, что последний вариант означал бы x = x=>y, что нарушает правило, запрещающее в одном блоке кода использовать имя с двумя значениями.

Мы надеемся, что вам понравятся эти новые операторы; я надеюсь, что мы скоро сможем выпустить второй предварительный выпуск компилятора, чтобы вы смогли попробовать, как эти операторы взаимодействуют с async/await!

.

.

.

.

ОБНОВЛЕНИЕ: ХА-ХА-ХА-ХА-ХА-ХА-ХА-ХА-ХА-ХА-ХА-ХА-ХА-ХА-ХА-ХА

Если не было понятно сразу, а также основываясь на комментариях типа: «Ты серьезно или это первоапрельская шутка?», скажу, что это была шутка; мы не собираемся добавлять все эти операторы. Хотя первая часть была абсолютно серьезной.

Одним из наиболее частых комментариев заключается в том, что многие считают конструкцию вида “??=” действительно полезной. Действительно, она была бы весьма полезной, хотя мы пока и не собираемся ее реализовывать. Этот пост навеян одним из комментариев к прошлогоднему первоапрельскому посту, в котором предложили оператор ??=.

Оригинал статьи