我刚刚编写了一个属性设置器,突然想到为什么当运算符 < 中可能涉及属性时,我们不必返回
set
的结果。 code>= 链接,即:(
var a = (b.c = d);
为了清楚起见,我添加了括号 - 但这在实践中没有区别)
我开始思考 - C# 编译器从哪里导出分配给 a< 的值/code> 在上面的例子中?
从逻辑上讲,它应该来自 (bc = d)
操作的结果,但由于它是使用 void set_blah(value)
方法实现的,所以不可能。
因此,唯一的其他选择是:
现在,在我看来,上面这行代码的正确阅读是
将a
设置为将bc
设置为d
的结果
我认为这是对代码的合理阅读 - 所以我想我应该测试一下是否这确实是一个稍微做作的测试所发生的情况 - 但问问自己是否认为它应该通过或失败:
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);
}
此测试失败。
仔细检查为代码生成的 IL 可以发现,true
值已加载到堆栈中,使用 dup
命令进行克隆,然后两个值均弹出连续的任务。
这种技术对于字段来说非常有效,但对我来说,对于属性来说似乎非常天真,因为每个属性实际上都是一个方法调用,而实际的最终属性值不能保证是输入值。
现在我知道很多人讨厌嵌套作业等,但事实是该语言允许您执行它们,因此它们应该按预期工作。
也许我真的很厚,但对我来说,这表明编译器(.Net 4 顺便说一句)对该模式的实现不正确。但我对代码的期望/阅读是否不正确?
I was just writing a property setter and had a brain-wave about why we don't have to return
the result of a set
when a property might be involved in operator =
chaining, i.e:
var a = (b.c = d);
(I've added the brackets for clarity - but it makes no difference in practise)
I started thinking - where does the C# compiler derive the value that is assigned to a
in the above example?
Logic says that it should be from the result of the (b.c = d)
operation but since that's implemented with a void set_blah(value)
method it can't be.
So the only other options are:
Now, to my mind, the correct reading of the above line of code is
set a
to the result of setting b.c
to d
I think that's a reasonable reading of the code - so I thought I'd test whether that is indeed what happens with a slightly contrived test - but ask yourself if you think it should pass or fail:
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);
}
This test fails.
Closer examination of the IL that is generated for the code shows that the true
value is loaded on to the stack, cloned with a dup
command and then both are popped off in two successive assignments.
This technique works perfectly for fields, but to me seems terribly naive for properties where each is actually a method call where the actual final property value is not guaranteed to be the input value.
Now I know many people hate nested assignments etc etc, but the fact is the language lets you do them and so they should work as expected.
Perhaps I'm being really thick but to me this suggests an incorrect implementation of this pattern by the compiler (.Net 4 btw). But then is my expectation/reading of the code incorrect?
发布评论
评论(3)
赋值
x = {expr}
的结果定义为从{expr}计算的值。并请注意,分配的值是已从
d
评估的值。因此,这里的实现是:虽然我也希望启用优化,但它将使用
dup
指令而不是局部变量。The result of an assignment
x = {expr}
is defined as the value evaluated from {expr}.And note that the value assigned is the value already evaluated from
d
. Hence the implementation here is:although I would also expect with optimisations enabled it will use the
dup
instruction rather than a local variable.我发现有趣的是,您的期望是疯狂分配 - 即分配两个不同的值,因为其中一个是具有异常行为的极其奇怪的属性 - 是理想的事态。
正如您所推断的,我们会尽一切努力避免这种状态。这是一件好事。当你说“x = y = z”时,如果可能的话,我们应该保证x和y最终分配相同的值——z的值——即使y是一些疯狂的东西,不包含你的值给它。 “x = y = z”在逻辑上应该类似于“y = z, x = z”,只不过 z 只计算一次。当赋值给 x 时,y 根本不涉及问题;为什么要这样呢?
另外,当然,当执行“x = y = z”时,我们不能始终如一地“重用”y,因为 y 可能是一个只写属性。如果没有 getter 来读取值怎么办?
另外,我注意到你说“这适用于字段”——而不是如果该字段是易失性的,则它不会。如果该字段是易失性字段,则您无法保证您分配的值就是该字段所采用的值。您无法保证过去从易失性字段中读取的值就是该字段现在的值。
有关此主题的更多想法,请参阅我的文章:
http://blogs.msdn.com/b/ericlippert/archive/2010/02/11/chaining-simple-assignments-is-not-so-simple.aspx
I find it interesting that your expectation is that the crazy assignment -- that is, assigning two different values because one of them is an extremely weird property with unusual behaviour -- is the desirable state of affairs.
As you've deduced, we do everything in our power to avoid that state. That is a good thing. When you say "x = y = z" then if at all possible we should guarantee that x and y end up assigned the same value -- that of z -- even if y is some crazy thing that doesn't hold the value you give it. "x = y = z" should logically be like "y = z, x = z", except that z is only evaluated once. y doesn't come into the matter at all when assigning to x; why should it?
Also, of course when doing "x = y = z" we cannot consistently "reuse" y because y might be a write only property. What if there is no getter to read the value from?
Also, I note that you say "this works for fields" -- not if the field is volatile it doesn't. You have no guarantee whatsoever that the value you assigned is the value that the field takes on if it is a volatile field. You have no guarantee that the value you read from a volatile field in the past is the value of the field now.
For more thoughts on this subject, see my article:
http://blogs.msdn.com/b/ericlippert/archive/2010/02/11/chaining-simple-assignments-is-not-so-simple.aspx
赋值运算符被记录为返回计算第二个操作数(在本例中为
b
)的结果。没关系,它还会将此值分配给其第一个操作数,并且此分配是通过调用返回void
的方法来完成的。规范说:
因此,实际上发生的情况是:
d
进行求值(我们将生成的值称为val
),bc
a
被赋予值val
,val
(但由于整个表达式在这里结束,它未使用)The assignment operator is documented to return the result of evaluating its second operand (in this case,
b
). It doesn't matter that it also assigns this value to its first operand, and this assignment is done by calling a method that returnsvoid
.The spec says:
So actually what happens is:
d
is evaluated (let's call the value producedval
)b.c
val
a
is assigned the valueval
val
(but since the whole expression ends here, it goes unused)