Цепочки вызовов простых операторов присваивания не такие уж и простые

Дополнение: Я прерываю этот эпизод « Невероятных приключений » в коде просьбой моего друга и коллеги Лукиана ( Lucian ), из команды VB , который интересуется, является ли распространенным использование в C # того факта, что присваивание является выражением ( expression ). Темой сообщения является наиболее распространенное использование данного шаблона: тот факт, что «цепочки вызовов» операторов присваивания вообще работают, является следствием того, что присваивания являются выражениями ( expressions ), а не операторами ( statements ). Существуют также и разные сценарии использования: кто-то может представить что-то вроде " returnthis . myField = x ;" в качестве более короткой записи для " this . myField = x ; returnthis . myField ;" для случая, когда мы выполняем некоторые вычисления и сохраняем результаты для последующего использования. Или, например, у нас есть что-то вроде myNonNullableString = ( myNullableString = Foo ()) ?? "< null >";. Существует несколько вариантов, когда эта идиома может применяться.

Сам я никогда не пользуюсь этой идиомой. Я считаю, что побочные эффекты присваивания, лучше всего видны при использовании отдельных операторов для каждого выражения, а не тогда, когда они встроены в более крупные конструкции. У меня к вам следующий вопрос: используете ли вы присваивание как выражение? Если да, как и почему? Обратите внимание, что мне нужны простые примеры использования этого шаблона в «реальной жизни», а не хитрые идеи о том, как он может быть теоретически использован. Если у вас есть такие примеры, пожалуйста, напишите их в комментариях, и я передам их Лукиану. Спасибо!

*********************

Сегодня я проверю еще один миф о языке C#. Давайте рассмотрим следующий код:

a = b = c;

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

int i, j, k;
i = j = k = 123;

Я часто слышу, что код работает «потому что оператор присваивания правоассоциативен, а результатом присваивания является значение правой части выражения».

На самом деле это высказывание верно только наполовину. Выражение правоассоциативно. Очевидно, что предыдущий пример эквивалентен следующему коду:

i = (j = (k = 123)));

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

const int x = 10;
short y;
object z;
z = y = x;
System.Console.WriteLine(z.GetType().ToString());

Этот фрагмент выводит “System.Int16”, а не “System.Int32”. Значение правой части “y = x” явно имеет тип int, но переменной z мы присваиваем не ссылку на упакованный int, а ссылку на упакованный short!

В таком случае, является ли корректным высказывание «…результатом присваивания является значение левой части выражения»?

Нет, это высказывание неверно и мы можем доказать это.

class C
{
  private string x;
  public string X {
    get { return x ?? ""; }
    set { x = value; } }
  static void Main()
  {
    C c = new C();
    object z;
    z = c.X = null;
    System.Console.WriteLine(z == null);
    System.Console.WriteLine(c.X == null);
  }
}

Этот фрагмент выводит “True / False”. Результатом оператора присваивания не является значение левой части выражения. Значение левой части – это пустая строка, но значение оператора равно null.

Черт возьми, левая часть выражения может даже не содержать значения. Свойства доступные только для записи загадочные и достаточно редкие, но вполне корректные. Если у нас бы не было метода получения значения свойства (getter), в таком случае левая часть выражения c.X не содержала бы значения!

Теперь достаточно просто сформулировать правильное высказывание: результатом простого оператора присваивания является значение, присвоенное левой части.

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