C# 堆栈中的意外操作顺序<;T>;相关一班轮

C# 堆栈中的意外操作顺序<;T>;相关一班轮,c#,stack,C#,Stack,通过在一行中调用Push()和Pop()堆栈的一个实例,与在两行中执行相同的代码相比,我得到了不同的行为 以下代码段复制了该行为: static void Main(string[] args) { Stack<Element> stack = new Stack<Element>(); Element e1 = new Element { Value = "one" }; Element e2 = new Element { Value = "two" }; s

通过在一行中调用
Push()
Pop()
堆栈的一个实例,与在两行中执行相同的代码相比,我得到了不同的行为

以下代码段复制了该行为:

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;
}
使用此代码,我得到以下结果(.NET3.5,WIN7,完全修补):

  • 调用
    预期()
    (版本为 两行)将一个元素保留在 堆叠
    设置为
    “两个”
  • 调用
    意外()时
    (版本 用一行)我得到一个元素
    值设置为的堆栈
    
    “一个”
我能想象的唯一不同的原因是运算符的优先级。由于赋值运算符(
=
)具有相同的属性,因此我看不出这两个方法行为不同的原因

我还查看了生成的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 
}
.method public hidebysing static void意外(class[System]System.Collections.Generic.Stack`1 Stack)cil托管
{
.maxstack 8
L_0000:ldarg.0
L_0001:callvirt实例!0[System]System.Collections.Generic.Stack`1::Peek()
L_0006:ldarg.0
L_0007:callvirt实例!0[System]System.Collections.Generic.Stack`1::Pop()
L_000c:callvirt实例字符串操作order.Element::get_Value()
L_0011:callvirt实例void OperationOrder.Element::set_值(字符串)
L_0016:ret
}
我不是一个IL破解者,但对我来说,这段代码看起来仍然不错,ans应该在堆栈上留下一个元素,其值设置为“2”

有人能解释一下为什么方法
Unexpected()
做了与
Expected()
不同的事情吗

非常感谢

卢卡斯

你的表达相当于

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

EDIT:正如Eric Lippert指出的,所有表达式都是从左到右求值的。

在C#中,操作数是从左到右求值的。总是从左到右。因此,=运算符的操作数从左到右求值。在您的“预期”示例中,Pop()表达式发生在Peek()表达式的语句之前运行的语句中。在“意外”示例中,Peek()表达式位于Pop()表达式的左侧,因此首先对其求值

SLaks answer注意到呼叫的接收者总是在呼叫的参数之前进行评估。这是正确的——这是因为调用的接收者总是在参数的左边!但是SLaks声称这与它是一个属性设置程序这一事实有关,这是不正确的。如果值是一个字段,则会得到完全相同的行为;包含字段访问权限的表达式位于指定值的左侧,因此首先计算

您提到了“优先权”,这表明您可能赞同一个完全虚构且完全不真实的概念,即优先权与执行顺序有关。事实并非如此。放弃你对这个神话的信仰。子表达式的执行顺序是从左到右。运算符的操作按优先顺序进行

例如,考虑f-(+)+G.()**()优先级高于+。在您的神话世界中,先执行高优先级操作,因此计算G(),然后计算H(),然后将它们相乘,然后是F(),然后是加法

这是完全错误的。跟我说:优先权与执行顺序无关。子表达式是从左到右求值的,因此我们首先求值F(),然后求值G(),然后求值H()。然后我们计算G()和H()结果的乘积。然后我们用F()的结果计算乘积的和。也就是说,此表达式等效于:

temp1 = F();
temp2 = G();
temp3 = H();
temp4 = temp2 * temp3;
result = temp1 + temp4;
=运算符是与任何其他运算符一样的运算符;一个低优先级运算符,但它是一个运算符。它的操作数是从左到右求值的,因为它是一个低优先级运算符,所以运算符(赋值)的效果比所有其他运算符的效果都要晚。操作符的作用和操作数的计算是完全不同的。前者按优先顺序进行。后者按从左到右的顺序进行

清楚了吗

更新:混淆优先级、关联性和执行顺序非常常见;许多在编程语言设计方面有着长期经验的书作者都犯了错误。C#对每一项都有非常严格的规定;如果您对这一切如何运作的更多细节感兴趣,您可能会对我就这一主题撰写的以下文章感兴趣:


这里的属性设置程序不是语法糖,不是运算符吗?我意识到,对于正常赋值(字段或本地),行为是相同的,但我想链接到规范的正确部分。@SLaks:是否将特定标记视为运算符是程序语法分析的一个事实。该运算符是碰巧解析为对属性设置器的调用还是对变量的赋值取决于语义分析,这是编译的后期阶段。当您有“foo.bar=blah;”时,=在语法上总是一个运算符,不管它的意思是什么。
temp1 = F();
temp2 = G();
temp3 = H();
temp4 = temp2 * temp3;
result = temp1 + temp4;