是否在using语句未定义的行为中修改值类型?

时间:2011-01-11 21:03:53

标签: c# struct using mutable specifications

这个真的是this question的分支,但我认为它应该得到自己的答案。

根据ECMA-334第15.13节(using声明,以下称为资源获取):

  

在a中声明的局部变量   资源获取是只读的,并且应包含初始化程序。一个   如果出现编译时错误   embedded语句尝试修改   这些局部变量(通过赋值   或++--运营商)或   将它们作为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 个答案:

答案 0 :(得分:3)

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

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

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

此外,我没有看到为什么更改值类型的内容会危及RA的原因。有人在意解释?

也许正在进行合成检查的人误读了标准; - )

马里奥

答案 1 :(得分:2)

我怀疑它编译和运行的原因是SetField(int)是函数调用,而不是赋值或refout参数调用。编译器无法知道(通常)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()
        {

        }
    }    

情况很奇怪。最重要的是,避免创建只读可变字段。