tags:

views:

135

answers:

2

Hi all

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

+4  A: 
SLaks
You're right. I should have thought of that.Thanks a lot!
Lukas Ith
+8  A: 

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

Eric Lippert
Isn't `=` syntactic sugar for the property setter here, and not an operator?
SLaks
I realize that the behavior would be same same for a normal assignment (field or local), but I wanted to link to the correct part of the spec.
SLaks
Perfectly clear now. And I'll remeber it - I promise!
Lukas Ith
@SLaks: Whether a particular token is considered an operator is a fact about the *syntactic analysis* of the program. Whether that operator happens to be resolved as a call to a property setter or an assignment to a variable depends on the *semantic analysis*, which is a later stage of compilation. When you have "foo.bar = blah;", the = is always syntactically an operator no matter what it means.
Eric Lippert