Неожиданный операционный порядок в Стеке <T> связал один лайнер

Путем вызова Push() и Pop() экземпляр Stack в одной строке я получаю другое поведение, чем выполнение, по моему скромному мнению, того же кода в двух строках.

Следующий фрагмент кода воспроизводит поведение:

static void Main(string[] args)
{
 Stack stack = new Stack();
 Element e1 = new Element { Value = "one" };
 Element e2 = new Element { Value = "two" };
 stack.Push(e1);
 stack.Push(e2);

 Expected(stack);  // element on satck has value "two"
 //Unexpected(stack);  // element on stack has value "one"

 Console.WriteLine(stack.Peek().Value);
 Console.ReadLine();
}

public static void Unexpected(Stack stack)
{
 stack.Peek().Value = stack.Pop().Value;
}

public static void Expected(Stack stack)
{
 Element e = stack.Pop();
 stack.Peek().Value = e.Value;
}

Класс Элемента является действительно основным:

public class Element
{
 public string Value
 {
  get;
  set;
 }
}

С этим кодом я получаю следующий результат (.NET 3.5, Win 7, полностью исправленный):

  • Вызов Expected() (версия с двумя строками), оставляет один элемент на стеке с Value набор к "two".
  • При вызове Unexpected() (Версия с одной строкой) я получаю один элемент на стеке с Value набор к "one".

Единственной причиной различия, которое я мог вообразить, был приоритет оператора. Как оператор присваивания (=) имеет самый низкий приоритет, я не вижу оснований, почему два метода должны вести себя по-другому.

Я также взглянул на сгенерированный IL:

.method public hidebysig static void Unexpected(class [System]System.Collections.Generic.Stack`1 stack) cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: callvirt instance !0 [System]System.Collections.Generic.Stack`1::Peek()
    L_0006: ldarg.0 
    L_0007: callvirt instance !0 [System]System.Collections.Generic.Stack`1::Pop()
    L_000c: callvirt instance string OperationOrder.Element::get_Value()
    L_0011: callvirt instance void OperationOrder.Element::set_Value(string)
    L_0016: ret 
}

Я не трещина IL, но для меня, этот код все еще смотрит, хороший ответ должен оставить один элемент на стеке с набором значений к "два".

Может любой объяснять меня причина почему метод Unexpected() делает что-то другое, чем Expected()?

Большое спасибо!

Lukas

5
задан Lukas Ith 16 February 2010 в 14:34
поделиться

2 ответа

Ваше выражение эквивалентно

stack.Peek().set_Value(stack.Pop().Value);

При вызове экземпляра для ссылочного типа, экземпляр оценивается первым .

РЕДАКТИРОВАТЬ : Как указывает Эрик Липперт, все выражения вычисляются слева направо.

5
ответ дан 18 December 2019 в 13:13
поделиться

В большинстве случаев это не следует делать. В зависимости от уровня транзакции создано условие гонки, в данном примере это не имеет большого значения, но данные могут быть изменены с первого выбора на обновление. И все, что вы сделали, это заставили SQL сделать больше работы

Лучший способ знать наверняка, это проверить два различия и посмотреть, какой из них дает вам соответствующую производительность.

-121--1292550-

Есть небольшой эффект, так как вы делаете один и тот же чек дважды, по крайней мере в вашем примере:

IF EXISTS(SELECT 1 FROM Contacs WHERE [Type] = 1)

Должен запросить, если есть, если true то:

UPDATE Contacs SET [Deleted] = 1 WHERE [Type] = 1

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

-121--1292553-

В операндах C # вычисляются слева направо. Всегда всегда слева направо. Поэтому операнды оператора = вычисляются слева направо. В «ожидаемом» примере выражение Pop () выполняется в операторе, который выполняется перед оператором выражения Peek (). В «неожиданном» примере выражение Peek () находится слева от выражения Pop (), поэтому оно вычисляется первым.

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

Вы упомянули «приоритет», который указывает на то, что вы, вероятно, подписываетесь под совершенно мифическим и совершенно не соответствующим действительности представлением о том, что приоритет имеет какое-то отношение к порядку исполнения. Он не . Откажитесь от своей веры в этот миф. Порядок выполнения вложенных выражений - слева направо . Работа операторов выполняется в порядке очередности.

Рассмотрим, например, F () + G () * H (). * имеет более высокий приоритет, чем +. В вашем мифическом мире сначала выполняется операция с более высоким приоритетом, поэтому вычисляется G (), затем H (), затем они умножаются, затем F (), затем сложение.

Это совершенно и совершенно неправильно. Скажите это при мне: приоритет не имеет ничего общего с порядком исполнения. Подэкспрессии вычисляются слева направо, поэтому сначала мы оцениваем F (), затем G (), затем H (). Затем вычисляем произведение результата G () и H (). Затем вычисляем сумму произведения с результатом F (). То есть это выражение эквивалентно:

temp1 = F();
temp2 = G();
temp3 = H();
temp4 = temp2 * temp3;
result = temp1 + temp4;

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

Ясно ли это?

ОБНОВЛЕНИЕ: Путать приоритет, ассоциативность и порядок выполнения чрезвычайно часто; многие авторы книг с большим опытом в дизайне языка программирования ошибаются. C # имеет очень строгие правила, определяющие каждый; если вас интересует более подробная информация о том, как все это работает, вам могут быть интересны эти статьи, которые я написал на эту тему:

http://blogs.msdn.com/ericlippert/archive/tags/precedence/default.aspx

9
ответ дан 18 December 2019 в 13:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: