这个真的是this question的分支,但我认为它应该得到自己的答案。
根据ECMA-334第15.13节(using
声明,以下称为资源获取):
在a中声明的局部变量 资源获取是只读的,并且应包含初始化程序。一个 如果出现编译时错误 embedded语句尝试修改 这些局部变量(通过赋值 或
++
和--
运营商)或 将它们作为ref
或out
传递 参数。
这似乎解释了为什么下面的代码是非法的。
struct Mutable : IDisposable
{
public int Field;
public void SetField(int value) { Field = value; }
public void Dispose() { }
}
using (var m = new Mutable())
{
// This results in a compiler error.
m.Field = 10;
}
但是这个怎么样?
using (var e = new Mutable())
{
// This is doing exactly the same thing, but it compiles and runs just fine.
e.SetField(10);
}
上述代码段是否在C#中未定义和/或非法?如果它是合法的,此代码与上述规范摘录之间的关系是什么?如果非法,为什么会有效?是否存在一些允许它的微妙漏洞,或者它的作用仅仅归功于运气(因此人们不应该依赖这些看似无害的代码的功能)?
答案 0 :(得分:3)
我会以这样的方式阅读标准
using( var m = new Mutable() )
{
m = new Mutable();
}
是被禁止的 - 原因似乎很明显。 为什么结构Mutable不允许它打败我。因为对于一个类,代码是合法的并且编译得很好......(对象类型我知道..)
此外,我没有看到为什么更改值类型的内容会危及RA的原因。有人在意解释?
也许正在进行合成检查的人误读了标准; - )
马里奥
答案 1 :(得分:2)
我怀疑它编译和运行的原因是SetField(int)
是函数调用,而不是赋值或ref
或out
参数调用。编译器无法知道(通常)SetField(int)
是否会改变变量。
根据规范,这似乎完全合法。
考虑其他选择。在C#编译器中,确定给定函数调用是否会改变值的静态分析显然是成本过高的。该规范旨在避免所有情况下的这种情况。
另一种选择是C#不允许对using
语句中声明的值类型变量进行任何方法调用。这可能不是一个坏主意,因为在结构上实现IDisposable
无论如何都只是在寻找麻烦。但是当C#语言最初开发时,我认为他们对以多种有趣方式使用结构体寄予厚望(正如您最初使用的GetEnumerator()
示例所示)。
答案 2 :(得分:2)
总结一下
struct Mutable : IDisposable
{
public int Field;
public void SetField( int value ) { Field = value; }
public void Dispose() { }
}
class Program
{
protected static readonly Mutable xxx = new Mutable();
static void Main( string[] args )
{
//not allowed by compiler
//xxx.Field = 10;
xxx.SetField( 10 );
//prints out 0 !!!! <--- I do think that this is pretty bad
System.Console.Out.WriteLine( xxx.Field );
using ( var m = new Mutable() )
{
// This results in a compiler error.
//m.Field = 10;
m.SetField( 10 );
//This prints out 10 !!!
System.Console.Out.WriteLine( m.Field );
}
System.Console.In.ReadLine();
}
因此,与上面所写的相反,我建议不要使用函数来修改using块中的结构。这似乎工作,但可能会停止在未来工作。
马里奥
答案 3 :(得分:2)
此行为未定义。在C#4.0规范部分7.6.4(成员访问)结束时的The C# Programming language中,Peter Sestoft表示:
两个项目符号表示“如果该字段是只读的......那么 结果是一个值“当有一个稍微令人惊讶的效果 field有一个struct类型,那个struct类型有一个可变字段(不是 建议的组合 - 请参阅此点上的其他注释。)
他提供了一个例子。我创建了自己的示例,其中显示了更多详细信息。
然后,他接着说:
有点奇怪的是,如果s是结构类型的局部变量 在using语句中声明,它也具有s的效果 不可变,然后s.SetX()按预期更新s.x.
在这里,我们看到其中一位作者承认这种行为是不一致的。根据7.6.4节,只读字段被视为值,不会更改(副本更改)。因为8.13节告诉我们使用语句将资源视为只读:
资源变量在嵌入语句中是只读的,
using
语句中的资源应该像readonly字段一样运行。根据7.6.4的规则,我们应该处理一个值而不是变量。但令人惊讶的是,资源的原始值确实发生了变化,如下例所示:
//Sections relate to C# 4.0 spec
class Test
{
readonly S readonlyS = new S();
static void Main()
{
Test test = new Test();
test.readonlyS.SetX();//valid we are incrementing the value of a copy of readonlyS. This is per the rules defined in 7.6.4
Console.WriteLine(test.readonlyS.x);//outputs 0 because readonlyS is a value not a variable
//test.readonlyS.x = 0;//invalid
using (S s = new S())
{
s.SetX();//valid, changes the original value.
Console.WriteLine(s.x);//Surprisingly...outputs 2. Although S is supposed to be a readonly field...the behavior diverges.
//s.x = 0;//invalid
}
}
}
struct S : IDisposable
{
public int x;
public void SetX()
{
x = 2;
}
public void Dispose()
{
}
}
情况很奇怪。最重要的是,避免创建只读可变字段。