通过在一行中调用Push()
和Pop()
Stack<T>
的实例,我得到的行为与在两行中执行imho相同代码的行为不同。
以下代码段重现了该行为:
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;
}
Element类非常基本:
public class Element
{
public string Value
{
get;
set;
}
}
使用此代码,我得到以下结果(.NET 3.5,Win 7,完全修补):
Expected()
(版本号为。{
两行)留下一个元素
将Value
设置为"two"
的堆栈。Unexpected()
时(版本
一行)我得到一个元素
设置为Value
的堆栈
"one"
。我能想到的差异的唯一原因是运算符优先级。由于赋值运算符(=
)具有lowest precedence,我认为没有理由为什么这两种方法的行为应该不同。
我还看了一下IL生成的内容:
.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
}
我不是IL破解,但对我来说这个代码看起来还不错,应该在堆栈上留下一个元素值设置为“2”。
有人可以解释一下方法Unexpected()
做出与Expected()
不同的原因吗?
非常感谢!
的Lukas
答案 0 :(得分:9)
在C#中,操作数从左到右进行评估。始终始终从左到右。因此,=运算符的操作数从左到右进行评估。在“预期”示例中,Pop()表达式发生在Peek()表达式语句之前运行的语句中。在“意外”示例中,Peek()表达式位于Pop()表达式的左侧,因此首先对其进行求值。
SLaks回答说明,呼叫的接收者总是在呼叫参数之前进行评估。这是正确的 - 那是因为呼叫的接收者总是在参数的左边!但是SLaks声称这与它的属性设置器不正确这一事实有关。如果Value是一个字段,你会得到完全相同的行为;包含字段访问权限的表达式位于所分配值的左侧,因此首先计算。
你提到了“优先权”,这表明你可能订阅了一个完全神话般的,完全不真实的概念,即优先权与执行顺序有关。 它没有。放弃自己对这个神话的信仰。子表达式的执行顺序是从左到右。操作符的操作按优先顺序进行。
例如,考虑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
答案 1 :(得分:5)
你的表达相当于
stack.Peek().set_Value(stack.Pop().Value);
击> <击> When calling an instance method on a reference type, the instance is evaluated first 击>
编辑:正如Eric Lippert指出的那样,所有表达式都是从左到右进行评估。