我刚刚写了一个属性设置器,并且当一个属性可能涉及运算符{{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顺便说一句)对这种模式的错误实现。但是,我的期望/读取代码是不正确的?
答案 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字段中读取的值现在是该字段的值。
有关此主题的更多想法,请参阅我的文章:
答案 2 :(得分:2)
记录赋值运算符以返回计算其第二个操作数的结果(在本例中为b
)。它也将此值赋给其第一个操作数并不重要,并且通过调用返回void
的方法来完成此赋值。
规范说:
14.14.1简单分配
=运算符称为简单赋值运算符。在一个简单的赋值中,右操作数应该是 是一个可隐式转换为该类型的类型的表达式 左操作数。该操作分配权限的值 操作数给出的变量,属性或索引器元素 左操作数。简单赋值表达式的结果是 赋值给左操作数的值。结果与...相同 左操作数,并且始终归类为值。
实际上发生的事情是:
d
(让我们调用生成的值val
)b.c
val
a
被分配了值val
val
(但由于整个表达式在此处结束,因此未使用)