Оператор '=' цепочка в C # - конечно, этот тест должен пройти? - PullRequest
18 голосов
/ 12 июля 2011

Я только что написал установщик свойств, и мне пришла в голову мысль, почему нам не нужно return результат set, когда свойство может быть задействовано в цепочке операторов =, то есть:

var a = (b.c = d);

(я добавил скобки для ясности - но на практике это не имеет значения)

Я задумался - откуда компилятор C # получает значение, присвоенное a в приведенном выше примере?

Логика говорит, что она должна быть результатом операции (b.c = d), но поскольку она реализована методом void set_blah(value), она не может быть.

единственные другие опции:

  • Перечитать b.c после присвоения и использовать это значение
  • Повторно использовать d

  • Редактировать (после ответа и комментариев Эрика) - есть третий вариант, который делает C #: используйте значение, записанное в b.c после того, как произошли какие-либо преобразования

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

установить a на результат установки b.c на d

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

public class TestClass
{
  private bool _invertedBoolean;
  public bool InvertedBoolean
  {
    get
    {
      return _invertedBoolean;
    }
    set
    {
      //don't ask me why you would with a boolean,
      //but consider rounding on currency values, or
      //properties which clone their input value instead
      //of taking the reference.
      _invertedBoolean = !value;
    }
  }
}

[TestMethod]
public void ExampleTest()
{
  var t = new TestClass();
  bool result;
  result = (t.InvertedBoolean = true);
  Assert.IsFalse(result);
}

Этот тест не пройден .

Более тщательное изучение IL, сгенерированного для кода, показывает, что значение true загружается в стек, клонируется с помощью команды dup, а затем оба выталкиваются в двух последовательных назначениях.

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

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

Возможно, я действительно толстый, но для меня это предполагаетправильная реализация этого шаблона компилятором (.Net 4 кстати).Но тогда мое ожидание / чтение кода неверно?

Ответы [ 3 ]

18 голосов
/ 12 июля 2011

Результатом присвоения x = {expr} является , определенное как значение, оцененное из {expr} .

§14.14.1 Простое присваивание (ECMA334 v4)

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

И обратите внимание, что значение , присвоенное , является значением , уже оцененным с d.Следовательно, реализация здесь такова:

var tmp = (TypeOfC)d;
b.c = tmp;
a = tmp;

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

16 голосов
/ 12 июля 2011

Мне интересно, что вы ожидаете, что сумасшедшее присваивание - то есть присваивание двух разных значений, потому что одно из них является чрезвычайно странным свойством с необычным поведением - желательно состояние дел.

Как вы уже поняли, мы делаем все возможное, чтобы избежать этого состояния. Это хорошая вещь. Когда вы говорите «x = y = z», тогда, если это вообще возможно, мы должны гарантировать, что x и y в конечном итоге присвоили одно и то же значение - значение z - даже если y - это какая-то безумная вещь, которая не содержит значения, которое вы дай это. «x = y = z» логически должно быть похоже на «y = z, x = z», за исключением того, что z оценивается только один раз. у вообще не вступает в дело при присвоении х; с чего бы это?

Также, конечно, при выполнении «x = y = z» мы не можем последовательно «повторно использовать» y, потому что y может быть свойство только для записи . Что если нет получателя для чтения значения?

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

Дополнительные мысли по этому вопросу см. В моей статье:

http://blogs.msdn.com/b/ericlippert/archive/2010/02/11/chaining-simple-assignments-is-not-so-simple.aspx

2 голосов
/ 12 июля 2011

Оператор присваивания задокументирован для возврата результата вычисления его второго операнда (в данном случае b). Не имеет значения, что он также присваивает это значение своему первому операнду, и это назначение выполняется путем вызова метода, который возвращает void.

В спецификации сказано:

14.14.1 Простое назначение

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

Так что на самом деле происходит:

  1. d оценивается (назовем произведенное значение val)
  2. результат присваивается b.c
  3. результат оператора присваивания val
  4. a присваивается значение val
  5. результат второго оператора присваивания также val (но поскольку здесь заканчивается все выражение, оно не используется)
...