自变只读结构字段
本文关键字:字段 结构 只读 | 更新日期: 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。