运算符'='在C#中链接 - 当然这个测试应该通过?

时间:2011-07-12 13:29:46

标签: c# compiler-construction operators

我刚刚写了一个属性设置器,并且当一个属性可能涉及运算符{{1}时,有一个关于为什么我们不必return set的结果的脑波。链接,即:

=

(为了清楚起见,我添加了括号 - 但在实践中没有区别)

我开始思考 - 在上面的例子中,C#编译器在哪里导出分配给var a = (b.c = d); 的值?

逻辑说它应该来自a操作的结果,但是因为它是用(b.c = d)方法实现的,所以它不可能。

所以唯一的其他选择是:

  • 在分配后重新阅读void set_blah(value)并使用该值
  • 重复使用b.c

  • 编辑(自Eric回答和评论) - 还有第三个选项,就是C#所做的:在转换后使用写入d的值已经发生了

现在,在我看来,正确阅读上面的代码行是

  

b.c设置为将a设置为b.c

的结果

我认为这是对代码的合理解读 - 所以我认为我会测试这是否确实是通过轻微设计的测试发生的事情 - 但问问自己是否认为它应该通过或失败:

d

此测试失败

对代码生成的IL的仔细检查表明,public class TestClass { private bool _invertedBoolean; public bool InvertedBoolean { get { return _invertedBoolean; } set { //don't ask me why you would with a boolean, //but consider rounding on currency values, or //properties which clone their input value instead //of taking the reference. _invertedBoolean = !value; } } } [TestMethod] public void ExampleTest() { var t = new TestClass(); bool result; result = (t.InvertedBoolean = true); Assert.IsFalse(result); } 值已加载到堆栈,使用true命令克隆,然后在两个连续分配中弹出两个值。

这种技术对于字段非常有效,但对于我来说,每个实际上都是一个方法调用的属性似乎非常天真,其中实际的最终属性值不能保证是输入值。

现在我知道很多人讨厌嵌套作业等等,但事实是语言允许你这样做,所以他们应该按预期工作。

也许我真的很厚,但对我而言这表明编译器(.Net 4顺便说一句)对这种模式的错误实现。但是,我的期望/读取代码是不正确的?

3 个答案:

答案 0 :(得分:18)

作业x = {expr}的结果是 定义 ,因为是从{expr} 评估的值。

  

§14.14.1简单分配(ECMA334 v4)

     

...   简单赋值表达式的结果是赋值的值   左操作数。结果与左操作数的类型相同,   并始终归类为值。   ...

请注意,分配的d中已评估的 。因此,这里的实施是:

var tmp = (TypeOfC)d;
b.c = tmp;
a = tmp;

虽然我也希望在启用优化的情况下,它会使用dup指令而不是局部变量。

答案 1 :(得分:16)

我发现有趣的是你的期望是疯狂的赋值 - 也就是说,分配两个不同的值,因为其中一个是非常奇怪的属性,具有异常行为 - 是理想的状态。

正如您所推断的那样,我们竭尽全力避免这种状态。这是的事情。当你说“x = y = z”时,如果可能的话,我们应该保证x和y最终被分配相同的值 - 即z的值 - 即使y是一些不具有价值的疯狂的东西你给它。 “x = y = z”在逻辑上应该类似于“y = z,x = z”,除了z仅被评估一次。在分配给x时,y根本没有涉及到这个问题;为什么要这样?

此外,当然,当做“x = y = z”时,我们不能始终“重用”y,因为y可能是只写属性。如果没有getter从中读取值,该怎么办?

另外,我注意到你说“这适用于字段” - 如果该字段是易变的,则不会。您无法保证所分配的值是该字段在易失性字段中所采用的值。您无法保证从过去的volatile字段中读取的值现在是该字段的值。

有关此主题的更多想法,请参阅我的文章:

http://blogs.msdn.com/b/ericlippert/archive/2010/02/11/chaining-simple-assignments-is-not-so-simple.aspx

答案 2 :(得分:2)

记录赋值运算符以返回计算其第二个操作数的结果(在本例中为b)。它也将此值赋给其第一个操作数并不重要,并且通过调用返回void的方法来完成此赋值。

规范说:

  

14.14.1简单分配

     

=运算符称为简单赋值运算符。在一个简单的赋值中,右操作数应该是   是一个可隐式转换为该类型的类型的表达式   左操作数。该操作分配权限的值   操作数给出的变量,属性或索引器元素   左操作数。简单赋值表达式的结果是   赋值给左操作数的值。结果与...相同   左操作数,并且始终归类为值。

实际上发生的事情是:

    评估
  1. d(让我们调用生成的值val
  2. 结果已分配到b.c
  3. 赋值运算符的结果为val
  4. a被分配了值val
  5. 第二个赋值运算符的结果也是val(但由于整个表达式在此处结束,因此未使用)