【问题标题】:Does foreach() iterate by reference?foreach() 是否通过引用进行迭代?
【发布时间】:2010-12-05 00:42:23
【问题描述】:

考虑一下:

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

obj 是对列表中相应对象的引用,以便当我更改属性时,更改将在对象实例中持续存在吗?

【问题讨论】:

  • EnumeratorCurrent 属性返回ref T 时,有foreach (ref var x in XXX) 语法,从C#7.3 开始可用(例如,在Span&lt;T&gt; 上进行迭代)。有关详细信息,请参阅@Wolf 的答案。

标签: c# reference foreach


【解决方案1】:

如果枚举器的 Current 属性返回一个引用类型,您可能会觉得到 C# 7.3 版时可以通过引用更改值对您来说很有趣。以下内容是有效的(来自 MS 文档的逐字副本):

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

在以下网址了解有关此新功能的更多信息 C# foreach statement | Microsoft Docs.

【讨论】:

    【解决方案2】:

    在这种情况下,您可以(使用 List&lt;T&gt;),但如果您要迭代通用 IEnumerable&lt;T&gt;,那么它将取决于其实现。

    例如,如果它仍然是 List&lt;T&gt;T[],那么一切都会按预期工作。 当您使用使用 yield 构造的 IEnumerable&lt;T&gt; 时,就会遇到大问题。在这种情况下,您不能再在一次迭代中修改 T 的属性,并且如果您再次迭代相同的 IEnumerable&lt;T&gt;,它们会出现。

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

      • 这很有趣。我想知道在这种情况下是否克隆了集合。
      • 使用foreach时...有2种情况。 1. 如果你的类实现它,它可以转换为 IEnumerable/IEnumerable。如果您的集合是一个结构,它将被装箱。 2.直接使用GetEnumerator()方法。这是一种方法模式。它允许在值类型上使用基本枚举器,而无需昂贵的装箱。 IEnumerable 根本不需要。但是,当然没有 linq(我们需要特征或某种值类型接口)。
      【解决方案4】:

      是的,直到您将泛型类型从 List 更改为 IEnumerable..

      【讨论】:

      • 有人愿意详细说明吗?
      • 我会进一步解释为什么会这样
      • 我认为这可能与此有关:stackoverflow.com/a/8064426/1054322
      • @MatrixManAtYrService 对于 this 引用的示例情况可能是正确的,其中 IEnumerable 作用于新构造的对象。据我所知,使用 IEnumerable 与 List 没有区别。
      【解决方案5】:

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

      【讨论】:

      • 哦,很高兴知道!阅读热门答案我想知道为什么我的代码不起作用,但这解释了它。将结构更改为类和 * magic * 它可以工作。
      【解决方案6】:

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

      但是请注意,您不能更改变量 obj 本身,因为它是迭代变量。如果你尝试,你会得到一个编译错误。这意味着您不能将其设为空,并且如果您正在迭代值类型,则不能修改任何成员,因为这会更改值。

      C# 语言规范声明 (8.8.4)

      "迭代变量对应 一个只读局部变量 范围延伸到嵌入式 声明。”

      【讨论】:

      • 即使对于值类型(即结构)也是如此。
      • @technophile:如果 MyClass 是一个结构体,实际上你不能修改 obj,因为你不能修改迭代变量本身。
      • @Brian Rasmussen:为什么我们不能修改可枚举对象?
      • @Amy:根据语言规范(8.8.4)“迭代变量对应于一个只读局部变量,其范围扩展到嵌入语句。”
      • 公共类 MyClass { public int val; } class MyApp { public static void Main(String[] args) { IList list = new List(); MyClass obj1 = new MyClass(); obj1.val = 10; list.Add(obj1); MyClass obj2 = new MyClass(); obj2.val = 10; list.Add(obj2); foreach (MyClass obj in list) { obj.val = 20; } } } 参考这段代码。这行得通;)
      【解决方案7】:

      是的,这也是为什么您不能在 foreach 语句的上下文中更改可枚举对象的原因。

      【讨论】:

        【解决方案8】:

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

        【讨论】:

        • 这不会对他感兴趣的功能产生影响。同一个列表的四个副本都将引用相同的实际对象,因此无论您是从 List1 还是从 List3 修改它们,都将导致被修改的原始对象。它唯一会影响的是,如果您在列表中添加或删除项目,这不会影响列表的任何副本。
        • 是的,简短的回答当然是“是的,它是一个 ref 类型”,但他可能遇到的问题是,如果他创建了一些原始列表的副本,那么如果他调用 get_the_list( );更改它然后调用 get_the_list();再次在代码的其他地方,他可能不会得到他预期的结果。
        【解决方案9】:

        您在这里提出了 2 个不同的问题,让我们按顺序来回答。

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

        如果您的意思与 C++ for 循环引用的含义相同,那么不是。 C# 没有与 C++ 相同的局部变量引用,因此不支持这种类型的迭代。

        是否会保留更改

        假设 MyClass 是引用类型,答案是肯定的。类是 .Net 中的引用类型,因此迭代变量是对一个变量的引用,而不是副本。对于值类型而言,情况并非如此。

        【讨论】:

          【解决方案10】:

          好吧,在不完全理解“通过引用迭代”的意思的情况下,我无法具体回答是或否,但我可以说,表面上发生的事情是 .net 框架正在构建一个“枚举器” " 每次客户端代码调用 foreach 时的类,在 foreach 的生命周期内,它维护一个指向被迭代集合的引用指针,并且每次你的 foreach 迭代时,ir“交付”一个项目并“增加”指针或在枚举器中引用下一项...

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

          【讨论】:

            猜你喜欢
            • 2022-01-15
            • 2014-07-20
            • 2011-11-10
            • 1970-01-01
            • 1970-01-01
            • 2013-09-12
            • 2021-04-12
            • 2015-06-30
            • 1970-01-01
            相关资源
            最近更新 更多