为什么结构中的迭代器可以修改它?

我发现允许在值类型中使用迭代器方法来修改this
但是,由于CLR的限制,调用方法没有看到修改。 ( this是通过值传递的)

因此,迭代器和非迭代器中的相同代码会产生不同的结果:

 static void Main() { Mutable m1 = new Mutable(); m1.MutateWrong().ToArray(); //Force the iterator to execute Console.WriteLine("After MutateWrong(): " + m1.Value); Console.WriteLine(); Mutable m2 = new Mutable(); m2.MutateRight(); Console.WriteLine("After MutateRight(): " + m2.Value); } struct Mutable { public int Value; public IEnumerable MutateWrong() { Value = 7; Console.WriteLine("Inside MutateWrong(): " + Value); yield break; } public IEnumerable MutateRight() { Value = 7; Console.WriteLine("Inside MutateRight(): " + Value); return new int[0]; } } 

输出:

 在MutateWrong()里面:7
在MutateWrong()之后:0

在MutateRight()里面:7
在MutateRight()之后:7 

为什么在迭代器中改变结构是不是编译器错误(或至少是警告)?
这种行为是一个微妙的陷阱,不容易理解。

匿名方法具有相同的限制, 根本无法使用this

注意: 可变结构是邪恶的 ; 这应该永远不会出现在实践中。

为了certificate警告的合理性,应该是程序员可能会得到意外结果的情况。 根据Eric Lippert的说法,“ 我们试图仅在那些我们几乎可以肯定地说代码被破坏,误导或无用的情况下保留警告。 ”这是一个警告会产生误导的实例。

让我们说你有这个完全有效 – 如果不是非常有用 – 对象:

 struct Number { int value; public Number(int value) { this.value = value; } public int Value { get { return value; } } // iterator that mutates "this" public IEnumerable UpTo(int max) { for (; value <= max; value++) yield return value; } } 

你有这个循环:

 var num = new Number(1); foreach (var x in num.UpTo(4)) Console.WriteLine(num.Value); 

你期望这个循环打印1,1,1,1 ,而不是1,2,3,4 ,对吗? 所以这个课程的工作方式完全符合您的预期 这是警告不合理的情况。

由于这显然不是代码被破坏,误导或无用的情况,您如何建议编译器生成错误或警告?

引用自己“可变结构是邪恶的”:)如果你为结构实现扩展方法,你会遇到同样的事情。 如果您尝试在扩展方法中修改结构,您仍将保持原始结构不变。 由于扩展方法签名如下所示,它有点不太令人惊讶:

 static void DoSideEffects(this MyStruct x) { x.foo = ... 

看着它我们意识到像参数传递之类的东西发生了,因此结构被复制了。 但是当你使用扩展时它看起来像:

 x.DoSideEffects() 

你会惊讶地发现你的变量x没有任何影响。 我认为在幕后你的yield构造做了类似于扩展的事情。 我会更难说出起始句:“结构是邪恶的”..一般来说;)

我对加布所说的有类似的想法。 至少在理论上似乎可以选择使用struct来表现类似方法,将该方法的局部变量封装为实例字段:

 struct Evens { int _current; public IEnumerable Go() { while (true) { yield return _current; _current += 2; } } } 

我的意思是,显然有点奇怪。 这让我想起过去遇到的一些想法,开发人员在这种方法中调制了一些奇怪的方法来调用方法,例如将方法的参数包装到一个对象中,然后让该对象调用方法向后移动,感觉。 我会说这大致是这个。

我并不是说这是明智之举 ,但它至少是一种以你所描述的方式使用this的方式,可能是有意的,并且在技术上会表现出正确的行为。

虽然我认为.net缺乏不需要修改’this’的方法的特殊属性,但是不清楚应该发生什么。 这样的属性可以用于不可变的类类型以及可变的结构。 使用此类属性标记的方法应仅可用于结构变量,字段和参数,而不能用于临时值。

我认为没有办法避免让迭代器按值捕获struct。 完全有可能在使用迭代器时,它所基于的原始结构可能不再存在。 另一方面,如果struct实现了一个inheritanceIEnumerable 的接口,但是还包含一个返回Value的函数,那么在使用枚举器之前将struct转换为该接口类型理论上可以允许迭代器保持对结构而不必重新复制其值; 如果枚举器按值复制结构,即使在这种情况下,我也不会感到惊讶。