Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected operation order in Stack<T> related one liner

Tags:

stack

c#

By calling Push() and Pop() an instance of Stack<T> in a single line I get a different behavior than performing the imho same code in two lines.

The following code snippet reproduces the behavior:

static void Main(string[] args)
{
 Stack<Element> stack = new Stack<Element>();
 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<Element> stack)
{
 stack.Peek().Value = stack.Pop().Value;
}

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

The Element class is really basic:

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

With this code I get the following result (.NET 3.5, Win 7, fully patched):

  • Calling Expected() (version with two lines) leaves one element on the stack with Value set to "two".
  • When calling Unexpected() (Version with one line) I get one element on the stack with the Value set to "one".

The only reason for the difference I could imagine was the operator precedence. As the assignment operator (=) has the lowest precedence I see no reason why the two method should behave differently.

I also had a look at the IL generated:

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

I'm not an IL crack, but for me this code still looks good ans should leave one element on the stack with value set to "two".

Can anyone explain me the reason why the method Unexpected() does something different than Expected()?

Thanks a lot!

Lukas

like image 816
Lukas Ith Avatar asked Feb 16 '10 14:02

Lukas Ith


Video Answer


2 Answers

In C# operands are evaluated left-to-right. Always always always left to right. Therefore operands of the = operator are evaluated left-to-right. In your "expected" example, the Pop() expression happens in a statement that runs before the Peek() expression's statement. In your "unexpected" example, the Peek() expression is to the left of the Pop() expression, so it is evaluated first.

SLaks answer notes that the receiver of a call is always evaluated before the arguments of the call. This is correct -- that's because the receiver of a call is always to the left of the arguments! But SLaks' claim that this has something to do with the fact that it's a property setter is incorrect. You'll get exactly the same behaviour if Value were a field; the expression containing the field access is to the left of the value being assigned, and therefore is calculated first.

You mentioned "precedence", which indicates that you probably subscribe to the completely mythical and utterly untrue notion that precedence has something to do with order of execution. It does not. Divest yourself of your belief in this myth. Order of execution of subexpressions is left to right. Operation of operators is done in precedence order.

For example, consider F() + G() * H(). * is higher precedence than +. In your mythical world the higher precedence operation gets done first, so G() is evaluated, then H(), then they are multiplied, then F(), then the addition.

This is completely and utterly wrong. Say it with me: precedence has nothing to do with order of execution. The subexpressions are evaluated left-to-right, so first we evaluate F(), then G(), then H(). Then we compute the product of the result of G() and H(). Then we compute the sum of the product with the result of F(). That is, this expression is equivalent to:

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

The = operator is an operator like any other; a low-precedence operator, but an operator. Its operands are evaluated left-to-right, and because it is a low-precedence operator, the effect of the operator -- the assignment -- is done later than the effects of all the other operators. The effect of the operator and the computation of its operands are completely different things. The former is done in precedence order. The latter is done in left-to-right order.

Is that clear?

UPDATE: Confusing precedence, associativity and order of execution is extremely common; many book authors with long experience in programming language design get it wrong. C# has very strict rules defining each; if you're interested in more details about how this all works, you might be interested in these articles I've written on the subject:

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

like image 167
Eric Lippert Avatar answered Oct 23 '22 04:10

Eric Lippert


Your expression is equivalent to

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

When calling an instance method on a reference type, the instance is evaluated first.

EDIT: As Eric Lippert points out, all expressions are evaluated left-to-right.

like image 39
SLaks Avatar answered Oct 23 '22 06:10

SLaks