11

这确实是这个问题的一个分支,但我认为它应该得到自己的答案。

根据 ECMA-334 的第 15.13 节(在using声明中,以下称为resource-acquisition):

在资源获取中声明的局部变量 是只读的,并且应该包括一个初始化器。如果嵌入语句尝试修改这些局部变量(通过赋值或++and--运算符)或将它们作为refout 参数传递,则会发生编译时错误。

这似乎解释了为什么下面的代码是非法的。

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# 中是否未定义和/或非法?如果它是合法的,那么这段代码和上面规范的摘录有什么关系?如果它是非法的,为什么它会起作用?是否有一些微妙的漏洞允许它,或者它的工作原理仅归因于运气(因此人们不应该依赖这种看似无害的代码的功能)?

4

4 回答 4

3

我会以这样的方式阅读标准

using( var m = new Mutable() )
{
   m = new Mutable();
}

被禁止 - 理由似乎很明显。为什么不允许 struct Mutable 它打败了我。因为对于一个类,代码是合法的并且编译得很好......(我知道的对象类型......)

另外,我看不出更改值类型的内容确实会危及 RA 的原因。有人愿意解释吗?

也许有人在做 syntx 检查只是误读了标准 ;-)

马里奥

于 2011-01-11T21:53:19.383 回答
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 块中的结构。这似乎可行,但将来可能会停止工作。

马里奥

于 2011-01-12T05:29:23.917 回答
2

我怀疑它编译和运行的原因SetField(int)是函数调用,而不是赋值refout参数调用。编译器无法(通常)知道是否SetField(int)要改变变量。

根据规范,这似乎完全合法。

并考虑替代方案。在 C# 编译器中,确定给定函数调用是否会改变值的静态分析显然成本过高。该规范旨在避免在所有情况下出现这种情况。

另一种选择是 C# 不允许对语句中using声明的值类型变量进行任何方法调用。这可能不是一个坏主意,因为IDisposable在结构上实现无论如何只是自找麻烦。但是当 C# 语言最初开发时,我认为他们对以许多有趣的方式使用结构寄予厚望(正如GetEnumerator()您最初使用的示例所示)。

于 2011-01-11T21:13:46.410 回答
2

此行为未定义。在C# 4.0 规范第 7.6.4 节(成员访问)末尾的C# 编程语言中,Peter Sestoft 指出:

当字段具有结构类型并且该结构类型具有可变字段(不是推荐的组合 - 请参阅关于这一点的其他注释)。

他提供了一个例子。我创建了自己的示例,该示例在下面显示了更多详细信息。

然后,他继续说:

有点奇怪的是,如果 s 是在 using 语句中声明的结构类型的局部变量,这也具有使 s 不可变的效果,那么 s.SetX() 会按预期更新 sx。

在这里,我们看到其中一位作者承认这种行为是不一致的。根据第 7.6.4 节,只读字段被视为值并且不会更改(副本更改)。因为第 8.13 节告诉我们使用语句将资源视为只读:

资源变量在嵌入语句中是只读的,

using语句中的资源应该表现得像只读字段。根据 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()
        {

        }
    }    

情况很奇怪。底线,避免创建只读的可变字段。

于 2013-02-20T23:01:53.517 回答