自变异只读结构域

时间:2011-04-09 09:33:43

标签: c# struct readonly

我知道Eric Lippert's blog post about situation 但我认为这是一个不同的情况,因为该领域自我而不是其领域。如果Enumerator只是readonly没有显示任何效果并且输出始终为0,你怎么解释调用MoveNext()?

 class SomeClass
    {
        private List<int> list;
        private [readonly] List<int>.Enumerator enumerator;

        public SomeClass()
        {
            list = new List<int>() { 1, 2, 3 };
            enumerator = list.GetEnumerator();
        }

        public int ReadValue()
        {
            if (enumerator.MoveNext())
                return enumerator.Current;
            return -1;

        }
    }
static void Main()
    {
        SomeClass c = new SomeClass();
        int value;
        while ((value = c.ReadValue()) > -1)
            MessageBox.Show(value.ToString());
    }

3 个答案:

答案 0 :(得分:10)

  

我认为情况不同

你错了。这正是我在您参考的博客文章中描述的情况。

在此重复我的分析:对结构的每个非静态方法调用都会使用一个名为“this”的“ref”参数。我们不在参数列表中显示“ref this”参数,但它是由编译器为您生成的。 ref传递的任何内容都必须是变量。由于readonly变量可能(并且在这种情况下,将会)通过调用进行变异,因此我们必须确保readonly变量永远不会被ref传递。当你在readonly结构上调用一个方法时,我们创建一个临时变量,将结构复制到临时变量,在方法调用中将ref作为“this”传递给临时变量,然后丢弃临时变量。这解释了你所看到的行为; MoveNext引起的每个突变都发生在一个副本上,然后被丢弃。

你能解释为什么这种情况 - 与我在博客中描述的情况完全相同 - 有什么不同吗?对于那些让他们与众不同的普查员,您认为有什么不同?

答案 1 :(得分:2)

如果我理解Eric Lippert关于您发布的博文,那么声明

if(enumerator.MoveNext())

首先制作枚举数的副本,在副本上执行MoveNext()。该副本死了,下一行:

return enumerator.Current;

返回原始枚举数的Current,而不是副本,这就是为什么你总是得到0

答案 2 :(得分:2)

readonly 关键字给C#编译器带来了困难。它无法知道MoveNext()和Current是否存在违反只读合同的副作用。 MoveNext()当然可以。因此,要生成有效代码,必须创建迭代器值的副本。在读取Current属性时,它会再次发生两次,一次是MoveNext()方法调用。您可以通过在程序上运行ildasm.exe轻松地看到这一点,该副本在Debug版本中命名为CS $ 0 $ 0001。

如果编译器至少为此代码生成警告,那将是很好的。虽然很难做到准确,但确实需要知道该成员是否有副作用。它不知道。 way 有太多的结构类型,其属性getter没有副作用,所以总是生成警告是不可行的。

让它知道的特性是 const 关键字,因为它在C ++中使用。可以将方法声明为const,以指示它不会更改对象的状态。我非常怀疑这个功能是否会成为C#语言,编写const-correct代码并不是那么容易,坦率地说,有点像皮塔饼。