在什么情况下foreach使用ref,在什么情况下foreach使用copy?
using System;
using System.Linq;
class A {
public int v;
}
class Program
{
static void Main() {
var ints = new int[] { 0, 1, 2 };
var array = ints.Select(i=>new A {v = i}).ToArray();
foreach(var a in array) {
a.v = 999;
}
var enumerable = ints.Select(i=>new A {v = i});
foreach(var a in enumerable) {
a.v = 999;
}
Console.WriteLine($"array.First = {array.First().v}");
Console.WriteLine($"enumerable.First = {enumerable.First().v}");
}
}
jdoodle.com/ia/Jce
输出:
array.First = 999
enumerable.First = 0
似乎在foreach(var a in enumerable) {
中a是copy而不是ref,而在foreach(var a in array) {
中a是ref。
有人能解释一下吗?
3条答案
按热度按时间fnvucqvd1#
这不是真的关于
foreach
。它与如何构造array
和enumerable
有更多的关系。foreach
在这两种情况下都做同样的事情(获取枚举器并调用MoveNext
和Current
来迭代可枚举对象)。正是enumerable
和array
之间的差异导致了输出的差异。array
是一个A[]
,所以如果你改变它的元素v
,然后得到第一个元素,你会明显地看到变化。A
是引用类型,所以foreach
中的a
是对数组中元素的引用。enumerable
是由Select
产生的。如果只是调用Select
,则不会执行任何实质性操作。它只是创建了一个IEnumerable<A>
,当在上枚举时,创建了一堆A
对象。这里重要的一点是,每当枚举enumerable
时,都会运行Select
lambda。如果你不列举它,什么也不会发生。这被称为deferred execution。因此,第二个
foreach
枚举enumerable
,创建一堆A
对象,然后更改这些A
的v
。这是一个重要的区别--A
对象不存储在任何地方,这与数组不同。在每次迭代之后,您都“丢弃”了A
对象。在最后调用
enumerable.First()
时,再次开始枚举enumerable
--这次只枚举一次,因为只需要第一个元素。enumerable
是什么?它通过运行Select
中的代码创建一个新的A
对象。mitkmikd2#
Enumerable.Select
使用延迟执行,并返回一个在使用foreach
或GetEnumerator
方法时“执行”的查询。.ToArray()
返回包含对象的[]
。第一个for循环遍历实际对象并修改元素的值,而第二个for循环投影序列中的每个元素。在
enumerable
上执行.First()
,再次投影元素,并使用原始ints
返回列表中的第一个元素vbkedwbf3#
正如其他人所说,这是延迟执行与非延迟执行的一个示例。
举个例子:
当我们执行
foreach
时,Select
中的代码将为a
中的每个项目运行(即,我们将枚举a
,并在每次抓取a
中的项目时运行控制台writeline和f + x)。一旦我们添加了
ToArray()
,求值就必须立即发生,这意味着我们不再延迟执行。在您的示例中,由于我们立即调用
ToArray()
,这意味着我们立即获得一堆对A
对象的引用。此外,由于求值发生在该行上,因此我们有一个存储它们的位置。对于
enumerable
变量,new A()
的求值导致我们每次在foreach
中迭代时都创建一个A
对象,但我们刚刚创建的A
对象不是原始enumerable
集合的一部分,因此它丢失了。如果您采用我的示例,并在
Select()
行上调用ToArray()
和不调用ToArray()
的情况下运行它,您会看得更清楚。