英文:
Does C# foreach create a copy of the element from an IEnumerable?
问题
在哪些情况下 foreach 使用引用,以及在哪些情况下 foreach 使用副本?
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}");
}
}
输出:
array.First = 999
enumerable.First = 0
似乎在 foreach(var a in enumerable) {
中,a 是副本而不是引用;而在 foreach(var a in array) {
中,a 是引用。
有人能解释一下这个吗?
英文:
In what cases foreach uses a ref, and in what cases foreach uses a 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
Output:
array.First = 999
enumerable.First = 0
Seems that in foreach(var a in enumerable) {
a is copy not a ref, while in foreach(var a in array) {
a is a ref.
Can someone explain this?
答案1
得分: 5
这实际上与foreach
无关。它更多地与您如何构造array
和enumerable
有关。在这两种情况下,foreach
执行相同的操作(获取枚举器并调用MoveNext
和Current
以遍历可枚举对象)。导致输出差异的是enumerable
和array
之间的区别。
array
是一个A[]
,因此如果您更改其元素的v
,然后获取其中一个元素的第一个元素,显然会看到更改。A
是引用类型,因此foreach
中的a
是对数组中元素的引用。
然而,enumerable
是由Select
生成的东西。如果只调用Select
,则不会执行任何实质性操作。它只会创建一个IEnumerable<A>
,当对其进行枚举时,它会创建一堆A
对象。这里的重要点在于,只有在枚举enumerable
时才会运行Select
lambda。如果您不枚举它,什么都不会发生。这被称为延迟执行。
因此,第二个foreach
枚举enumerable
,创建了一堆A
对象,然后更改了这些A
的v
。这里的重要区别在于 - A
对象不会被存储在任何地方,不像数组。在每次迭代后,您都"丢弃"了A
对象。
当您在最后调用enumerable.First()
时,您再次开始枚举enumerable
- 这次只有一次,因为您只想要第一个元素。enumerable
会做什么呢?它通过运行Select
中的代码创建一个新的A
对象。
英文:
This is not really about foreach
. It has more to do with how you constructed array
and enumerable
. foreach
does the same thing in both cases (getting the enumerator and calling MoveNext
and Current
to iterate through the enumerable). It is the difference between enumerable
and array
that causes the difference in output.
array
is an A[]
, so if you change the v
s of its elements, and then get the first of those element, you will obviously see the change. A
is a reference type, so a
in the foreach
is a reference to the element in the array.
enumerable
however, is something produced by Select
. Nothing substantial is done if you just call Select
. It just creates an IEnumerable<A>
, that when enumerated over, creates a bunch of A
objects. The important point here is that the Select
lambda is run whenever enumerable
is being enumerated. If you don't enumerate it, nothing happens. This is called deferred execution.
So the second foreach
enumerates enumerable
, creating a bunch of A
objects, and you then change the v
s of those A
s. Here is the important difference - the A
objects don't get stored anywhere, unlike with an array. You "threw" away the A
object after each iteration.
When you call enumerable.First()
at the end, you start enumerating enumerable
again - only once this time because you want only the first element. And what does enumerable
do? It creates a new A
object by running the code in Select
.
答案2
得分: 1
Enumerable.Select
使用延迟执行,并返回一个在使用foreach
或GetEnumerator
方法时被"执行"的查询。.ToArray()
返回包含对象的[]
。
第一个for
循环迭代实际对象并修改元素的值,而第二个for
循环则对序列的每个元素进行投影。在enumerable
上执行.First()
会再次对元素进行投影,并使用原始的ints
返回列表中的第一个元素。
英文:
Enumerable.Select
uses deferred execution and returns a query that is "executed" while using the foreach
or GetEnumerator
method. .ToArray()
returns an []
that contains the objects.
The first for loop iterates over the actual objects and modifies the values of the elements, while the second for loop projects each element of the sequence. Executing .First()
on enumerable
, projects the elements again and uses the original ints
to return the first element from the list
答案3
得分: 1
以下是翻译好的部分:
正如其他人所说,这是延迟执行与非延迟执行的实例。
以以下示例为例:
internal class Program
{
// 注意这在 Main 中的设置位置!
static int x = 0;
private static void Main(string[] args)
{
int[] arr = { 1, 2, 3 };
var a = arr.Select(f => {
Console.WriteLine("已评估");
return f + x;
});
// 这会影响上面的 Select 如何评估事物。
x = 5;
foreach (var d in a)
{
Console.WriteLine(d);
}
}
}
当我们执行 foreach
时,Select
中的代码将针对 a
中的每个项目运行(即,我们将枚举 a
并在每次获取 a
中的项目时运行控制台写入和 f + x)。
一旦我们添加 ToArray()
,评估必须立即发生,这意味着我们不再延迟执行。
在你的示例中,由于我们立即调用 ToArray()
,这意味着我们立即获得了许多对 A
对象的引用。此外,由于评估发生在该行上,我们有一个存储它们的地方。
对于你的 enumerable
变量,new A()
的评估意味着我们在 foreach
中每次迭代时都创建一个 A
对象,但我们刚刚创建的 A
对象不是原始的 enumerable
集合的一部分,因此它会丢失。
如果你采用我的示例并在具有 Select()
的行上运行它,带有和不带有对 ToArray()
的调用,你会更清楚地看到这些情况。
英文:
As others have said, this is an instance of deferred execution vs non-deferred execution.
Take the following example:
internal class Program
{
// Note where this gets set in Main!
static int x = 0;
private static void Main(string[] args)
{
int[] arr = { 1, 2, 3 };
var a = arr.Select(f => {
Console.WriteLine("Evaluated");
return f + x;
});
// This will affect how the above Select evaluates things.
x = 5;
foreach (var d in a)
{
Console.WriteLine(d);
}
}
}
When we do our foreach
, the code in the Select
will be run for each item in a
(i.e., we will enumerate over a
and run the console writeline and f + x each time we grab an item in a
).
As soon as we tack on ToArray()
, the evaluation has to happen immediately, meaning we no longer defer execution.
In your example, since we call ToArray()
immediately, it means we immediately get a bunch of references to A
objects. Additionally, since the evaluation happens on that line, we have a place that we're storing them.
With your enumerable
variable, the evaluation of new A()
results in us creating an A
object each time we iterate in the foreach
, but the A
object we just made isn't apart of the original enumerable
collection, so it's lost.
You'll see things more clearly if you take my example and run it with and without a call to ToArray()
on the line with Select()
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论