通过调用Push()和Pop()实例Stack<T>在一行中我得到比在两行执行恕我直言相同的代码不同的行为.
以下代码段重现了该行为:
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;
}
Run Code Online (Sandbox Code Playgroud)
Element类非常基本:
public class Element
{
public string Value
{
get;
set;
}
}
Run Code Online (Sandbox Code Playgroud)
使用此代码,我得到以下结果(.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<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
}
Run Code Online (Sandbox Code Playgroud)
我不是IL破解,但对我来说这个代码看起来还不错,应该在堆栈上留下一个元素,值设置为"2".
任何人都可以解释一下这个方法Unexpected()有什么不同的原因Expected()吗?
非常感谢!
卢卡斯
在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;
Run Code Online (Sandbox Code Playgroud)
=运算符与其他任何运算符一样; 低优先级运算符,但是运算符.它的操作数从左到右进行评估,因为它是一个低优先级运算符,所以运算符的效果 - 赋值 - 比所有其他运算符的效果要晚.运算符的效果和操作数的计算是完全不同的事情.前者按优先顺序完成.后者按从左到右的顺序完成.
明白了吗?
更新:令人困惑的优先级,关联性和执行顺序非常普遍; 许多具有丰富编程语言设计经验的书籍作者都错了.C#有非常严格的规则来定义每个; 如果你对这一切有用的更多细节感兴趣,你可能会对我在这个主题上写的这些文章感兴趣:
http://blogs.msdn.com/ericlippert/archive/tags/precedence/default.aspx
你的表达相当于
stack.Peek().set_Value(stack.Pop().Value);
Run Code Online (Sandbox Code Playgroud)
编辑:正如Eric Lippert指出的那样,所有表达式都是从左到右评估的.
| 归档时间: |
|
| 查看次数: |
222 次 |
| 最近记录: |