自变只读结构字段

本文关键字:字段 结构 只读 | 更新日期: 2023-09-27 17:49:27

我注意到了Eric Lippert关于情况的博文但我认为这是一个不同的情况,因为这个领域改变了自己,而不是它的领域。如果Enumerator是只读的,你怎么解释调用MoveNext()没有显示任何效果,输出总是0 ?

 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());
    }

自变只读结构字段

我认为这是一个不同的情况

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

在这里重复一下我的分析:结构体上的每个非静态方法调用都有一个名为this的ref参数。我们没有在参数列表中显示"ref this"参数,但它是存在的,由编译器为您生成。ref传递的任何东西都必须是变量。因为一个只读变量可以(在这种情况下,将会)被调用改变,我们必须确保一个只读变量永远不会被ref传递。当你在一个只读结构体上调用一个方法时,我们创建一个临时变量,将这个结构体复制到临时变量中,在方法调用时将一个ref作为"this"传递给临时变量,然后丢弃临时变量。这解释了你所看到的行为;由MoveNext引起的每个突变都发生在一个副本上,然后被丢弃。

你能解释一下为什么这种情况——和我在博客中描述的完全一样——有什么不同吗?你认为枚举器的不同之处是什么?

如果我没理解你在Eric Lippert博客上发表的关于这个问题的文章的话,

if(enumerator.MoveNext())

首先生成一个enumerator的副本,然后在副本上执行moveext()。该副本终止,下一行:

return enumerator.Current;

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

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

如果编译器至少为这段代码生成一个警告就好了。很难做到准确,它确实需要知道成员是否有副作用。它不知道。方式太多带有属性getter的结构类型没有副作用,所以总是生成警告是不可行的。

需要让它知道的特性是c++中使用的const关键字。可以将方法声明为const,以表明它不会改变对象的状态。我非常怀疑这个特性是否会被引入c#语言,因为编写const正确的代码并不是那么容易的,坦率地说,这有点像pita。