foreach()是否通过引用进行迭代?


71

考虑一下:

List<MyClass> obj_list = get_the_list();
foreach( MyClass obj in obj_list )
{
    obj.property = 42;
}

obj对列表中相应对象的引用,以便当我更改属性时,更改一旦在某处构造后将保留在对象实例中?


Answers:


69

是的,obj是对集合中当前对象的引用(假设MyClass实际上是一个类)。如果您通过引用更改了任何属性,则就像您期望的那样,您也在更改对象。

但是请注意,您不能更改变量obj本身,因为它是迭代变量。如果尝试,将出现编译错误。这意味着您不能将其为null,并且如果要迭代值类型,则不能修改任何成员,因为那样会更改值。

C#语言规范的状态(8.8.4)

“迭代变量对应于只读局部变量,其范围扩展到嵌入式语句。”


1
即使对于值类型(即结构)也是如此。
Technophile

7
@technophile:实际上,如果MyClass是结构,则不能修改obj,因为不允许您修改迭代变量本身。
布赖恩·拉斯穆森

@Brian Rasmussen:为什么我们不能修改可枚举对象?
阿米特(Amit)

2
@Amy:根据语言规范(8.8.4),“迭代变量对应于只读局部变量,其范围扩展到嵌入式语句上。”
布赖恩·拉斯穆森

公共类MyClass {public int val; }类MyApp {公共静态void Main(String [] args){IList <MyClass> list = new List <MyClass>(); MyClass obj1 =新的MyClass(); obj1.val = 10; list.Add(obj1); MyClass obj2 =新的MyClass(); obj2.val = 10; list.Add(obj2); foreach(清单中的MyClass obj){obj.val = 20; 引用此代码。这有效;)
Amit


21

您在这里问了2个不同的问题,让我们按顺序进行。

foreach循环是否通过引用进行迭代?

如果您的意思与按引用进行循环的C ++相同,则否。C#没有与C ++相同的局部变量引用,因此不支持这种类型的迭代。

变更会持续存在吗

假设MyClass是引用类型,答案是肯定的。类是.Net中的引用类型,因此迭代变量是对一个变量的引用,而不是副本。对于值类型,这不是正确的。


17

好吧,发生在我遍历var集合时,我的更改没有在foreach循环中更新:

var players = this.GetAllPlayers();
foreach (Player player in players)
{
    player.Position = 1;
}

当我将var更改为List时,它开始工作。


那很有意思。我想知道是否在这种情况下克隆了该集合。
sharkin 2011年

使用foreach时...有2种情况。1.如果您的类实现了它,则可以将其强制转换为IEnumerable / IEnumerable <T>。如果您的收藏是一个结构,它将被装箱。2.直接使用GetEnumerator()方法。这是一种方法模式。它允许对值类型使用基本的枚举数,而无需进行昂贵的装箱。根本不需要IEnumerable。但是,当然没有限制(我们需要特征或某种类型的值类型接口)。
Arek Bal '18

9

在这种情况下,您可以(使用List<T>),但是如果要遍历泛型,IEnumerable<T>则泛型将取决于其实现。

如果它仍然是一个List<T>T[]举例来说,一切都会按预期工作。当您IEnumerable<T>使用使用yield构造的对象时,就会遇到大麻烦。在这种情况下,您将无法再修改T迭代中的属性,并且如果您IEnumerable<T>再次对其进行迭代,则期望它们存在。


4

只要不是结构,这都是正确的。


1
哦,很高兴知道!阅读最重要的答案,我想知道为什么我的代码不起作用,但这可以解释它。将结构更改为类,并且*魔术*起作用。
卢克,

4

也许有趣的是,在C#7.3版中,只要枚举数的Current属性返回引用类型,就可以按引用更改值。以下内容将是有效的(MS文档的普通照会):

Span<int> storage = stackalloc int[10];
int num = 0;
foreach (ref int item in storage)
{
    item = num++;
}

C#foreach语句中了解有关此新功能的更多信息 | 微软文档


2

好吧,在不完全了解“通过引用进行迭代”的含义的情况下,我无法明确回答是或否,但是我可以说,表面上的情况是.net框架正在为该类构造“枚举器”类每次客户端代码在foreach的生命周期内调用一个foreach时,都会在被迭代的集合中维护一个引用指针,并且每次您的foreach进行迭代时,ir都会“传递”一项并“递增”该指针或引用。列举下一项...

无论您要遍历的集合中的项目是值类型还是引用类型,都会发生这种情况。


0

obj是对列表中某个项目的引用,因此,如果更改它的值,它将保持不变。现在,您应该关注的是是否get_the_list();。正在制作列表的深层副本或返回相同的实例。


1
这不会影响他感兴趣的功能。同一列表的四个副本都将引用相同的实际对象,因此无论您是从List1还是List3修改它们,都将导致原始对象被修改。唯一会影响的是,如果您向列表中添加项目或从列表中删除项目,则不会影响列表的任何副本。
Technophile

是的,简短的答案当然是“是的,它是引用类型”,但是他可能会遇到的问题是,如果他创建了一些原始列表的副本,那么如果他调用get_the_list(); 更改它,然后调用get_the_list(); 再在代码中的其他地方,他可能无法获得预期的结果。
Stan R.

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.