代码之家  ›  专栏  ›  技术社区  ›  Polo

在“foreach”循环中修改列表的最佳方法是什么?

  •  60
  • Polo  · 技术社区  · 15 年前

    C/.NET 4.0中的一个新功能是,您可以在 foreach 没有得到例外。查看保罗·杰克逊的博客条目 An Interesting Side-Effect of Concurrency: Removing Items from a Collection While Enumerating 有关此更改的信息。

    做以下工作的最佳方法是什么?

    foreach(var item in Enumerable)
    {
        foreach(var item2 in item.Enumerable)
        {
            item.Add(new item2)
        }
    }
    

    通常我用 IList 作为缓存/缓冲区,直到 前额 但是有更好的方法吗?

    11 回复  |  直到 6 年前
        1
  •  65
  •   Rik elirevach    15 年前

    foreach中使用的集合是不可变的。这在设计上很重要。

    正如上面所说的 MSDN :

    foreach语句用于 循环访问集合以获取 你想要的信息,但可以 不用于添加或删除项 从源集合中避免 不可预测的副作用。 如果你 需要添加或删除 源集合,使用for循环。

    岗位在 link poko提供的指示在新的并发集合中允许这样做。

        2
  •  14
  •   tvanfosson    15 年前

    使用IEnumerable扩展方法复制枚举,然后对其进行枚举。这将向该枚举添加每个内部可枚举元素的副本。

    foreach(var item in Enumerable)
    {
        foreach(var item2 in item.Enumerable.ToList())
        {
            item.Add(item2)
        }
    }
    
        3
  •  6
  •   eulerfx    15 年前

    如前所述,但使用代码示例:

    foreach(var item in collection.ToArray())
        collection.Add(new Item...);
    
        4
  •  6
  •   Roland Pihlakas    9 年前

    为了说明日本龙的答案:如果你想 添加 将新项目添加到列表中,并希望在相同的枚举过程中也处理新添加的项目,然后您可以使用 对于 循环代替 前额 循环,问题已解决:)

    var list = new List<YourData>();
    ... populate the list ...
    
    //foreach (var entryToProcess in list)
    for (int i = 0; i < list.Count; i++)
    {
        var entryToProcess = list[i];
    
        var resultOfProcessing = DoStuffToEntry(entryToProcess);
    
        if (... condition ...)
            list.Add(new YourData(...));
    }
    

    对于可运行的示例:

    void Main()
    {
        var list = new List<int>();
        for (int i = 0; i < 10; i++)
            list.Add(i);
    
        //foreach (var entry in list)
        for (int i = 0; i < list.Count; i++)
        {
            var entry = list[i];
            if (entry % 2 == 0)
                list.Add(entry + 1);
    
            Console.Write(entry + ", ");
        }
    
        Console.Write(list);
    }
    

    最后一个示例的输出:

    0,1,2,3,4,5,6,7,8,9,1,3,5,7,9,

    列表(15项)














        5
  •  3
  •   Peter Mortensen venu    12 年前

    以下是您如何做到这一点(快速而肮脏的解决方案)。如果你 真的? 需要这种行为,您应该重新考虑您的设计或覆盖所有 IList<T> 成员并聚合源列表):

    using System;
    using System.Collections.Generic;
    
    namespace ConsoleApplication3
    {
        public class ModifiableList<T> : List<T>
        {
            private readonly IList<T> pendingAdditions = new List<T>();
            private int activeEnumerators = 0;
    
            public ModifiableList(IEnumerable<T> collection) : base(collection)
            {
            }
    
            public ModifiableList()
            {
            }
    
            public new void Add(T t)
            {
                if(activeEnumerators == 0)
                    base.Add(t);
                else
                    pendingAdditions.Add(t);
            }
    
            public new IEnumerator<T> GetEnumerator()
            {
                ++activeEnumerators;
    
                foreach(T t in ((IList<T>)this))
                    yield return t;
    
                --activeEnumerators;
    
                AddRange(pendingAdditions);
                pendingAdditions.Clear();
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                ModifiableList<int> ints = new ModifiableList<int>(new int[] { 2, 4, 6, 8 });
    
                foreach(int i in ints)
                    ints.Add(i * 2);
    
                foreach(int i in ints)
                    Console.WriteLine(i * 2);
            }
        }
    }
    
        6
  •  2
  •   Peter Mortensen venu    8 年前

    LINQ 对于处理收藏品非常有效。

    我不清楚你的类型和结构,但我会尽力使你的例子符合我的能力。

    从代码中可以看出,对于每个项,您都在向该项添加来自其“可枚举”属性的所有内容。这很简单:

    foreach (var item in Enumerable)
    {
        item = item.AddRange(item.Enumerable));
    }
    

    作为一个更一般的例子,假设我们想迭代一个集合并删除某些条件为真的项。避免 foreach ,使用LINQ:

    myCollection = myCollection.Where(item => item.ShouldBeKept);
    

    是否基于每个现有项添加项?没问题:

    myCollection = myCollection.Concat(myCollection.Select(item => new Item(item.SomeProp)));
    
        7
  •  1
  •   Josh G    15 年前

    枚举可枚举集合时不能更改该集合,因此必须在枚举之前或之后进行更改。

    这个 for 循环是个不错的选择,但是如果 IEnumerable 集合未实现 ICollection ,这是不可能的。

    要么:

    1)先复制收藏。枚举复制的集合,并在枚举期间更改原始集合。(@坦凡弗森)

    2)保存更改列表,并在枚举后提交。

        8
  •  1
  •   supercat    12 年前

    从性能角度来看,最好的方法可能是使用一个或两个数组。将列表复制到数组中,对数组执行操作,然后从数组中构建新列表。访问数组元素比访问列表项更快,并且 List<T> 和A T[] 可以使用快速的“批量复制”操作,从而避免与访问单个项目相关的开销。

    例如,假设您有一个 List<string> 希望列表中所有以 T 后面跟着一个项目“boo”,而所有以“u”开头的字符串都会被完全删除。最佳方法可能是:

    int srcPtr,destPtr;
    string[] arr;
    
    srcPtr = theList.Count;
    arr = new string[srcPtr*2];
    theList.CopyTo(arr, theList.Count); // Copy into second half of the array
    destPtr = 0;
    for (; srcPtr < arr.Length; srcPtr++)
    {
      string st = arr[srcPtr];
      char ch = (st ?? "!")[0]; // Get first character of string, or "!" if empty
      if (ch != 'U')
        arr[destPtr++] = st;
      if (ch == 'T')
        arr[destPtr++] = "Boo";
    }
    if (destPtr > arr.Length/2) // More than half of dest. array is used
    {
      theList = new List<String>(arr); // Adds extra elements
      if (destPtr != arr.Length)
        theList.RemoveRange(destPtr, arr.Length-destPtr); // Chop to proper length
    }
    else
    {
      Array.Resize(ref arr, destPtr);
      theList = new List<String>(arr); // Adds extra elements
    }
    

    如果 列表& T; 提供了一种从数组的一部分构造列表的方法,但我不知道有什么有效的方法可以这样做。不过,对数组的操作还是相当快的。需要注意的是,从列表中添加和删除项不需要在其他项周围“推送”;每个项都直接写入到数组中相应的位置。

        9
  •  0
  •   Bugs Manjeet    7 年前

    你真的应该用 for() 而不是 foreach() 在这种情况下。

        10
  •  0
  •   DDiVita    6 年前

    要添加到Timo的答案中,Linq也可以这样使用:

    items = items.Select(i => {
    
         ...
         //perform some logic adding / updating.
    
         return i / return new Item();
         ...
    
         //To remove an item simply have logic to return null.
    
         //Then attach the Where to filter out nulls
    
         return null;
         ...
    
    
    }).Where(i => i != null);
    
        11
  •  0
  •   pravin ghare    6 年前

    我写了一个简单的步骤,但是因为这个性能会降低

    这是我的代码段:

    for (int tempReg = 0; tempReg < reg.Matches(lines).Count; tempReg++)
                                {
                                    foreach (Match match in reg.Matches(lines))
                                    {
                                        var aStringBuilder = new StringBuilder(lines);
                                        aStringBuilder.Insert(startIndex, match.ToString().Replace(",", " ");
                                        lines[k] = aStringBuilder.ToString();
                                        tempReg = 0;
                                        break;
                                    }
                                }