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

发布于 2024-07-16 16:12:02 字数 528 浏览 12 评论 0原文

C# / .NET 4.0 中的一项新功能是,您可以在 foreach 中更改枚举而不会出现异常。 请参阅 Paul Jackson 的博客文章 并发的有趣副作用:枚举时从集合中删除项目了解有关此更改的信息。

执行以下操作的最佳方法是什么?

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

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

A new feature in C# / .NET 4.0 is that you can change your enumerable in a foreach without getting the exception. See Paul Jackson's blog entry An Interesting Side-Effect of Concurrency: Removing Items from a Collection While Enumerating for information on this change.

What is the best way to do the following?

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

Usually I use an IList as a cache/buffer until the end of the foreach, but is there better way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

甜尕妞 2024-07-23 16:12:03

在本例中使用 IEnumerable 扩展方法创建枚举的副本,然后对其进行枚举。 这会将每个内部可枚举中的每个元素的副本添加到该枚举中。

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

Make a copy of the enumeration, using an IEnumerable extension method in this case, and enumerate over it. This would add a copy of every element in every inner enumerable to that enumeration.

foreach(var item in Enumerable)
{
    foreach(var item2 in item.Enumerable.ToList())
    {
        item.Add(item2)
    }
}
无悔心 2024-07-23 16:12:03

为了说明 Nippysaurus 的答案:如果您要将新项目添加到列表中,并希望在同一枚举期间也处理新添加的项目,那么您只需使用 for循环而不是 foreach 循环,问题解决了:)

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 项)

0

1

2

3

4

5

6

7

8

9

1

3

5

7

9

To illustrate Nippysaurus's answer: If you are going to add the new items to the list and want to process the newly added items too during the same enumeration then you can just use for loop instead of foreach loop, problem solved :)

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(...));
}

For runnable example:

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);
}

Output of last example:

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

List (15 items)

0

1

2

3

4

5

6

7

8

9

1

3

5

7

9

且行且努力 2024-07-23 16:12:03

如前所述,但有一个代码示例:

foreach(var item in collection.ToArray())
    collection.Add(new Item...);

As mentioned, but with a code sample:

foreach(var item in collection.ToArray())
    collection.Add(new Item...);
森林迷了鹿 2024-07-23 16:12:03

在这种情况下,您确实应该使用 for() 而不是 foreach()

You should really use for() instead of foreach() in this case.

小鸟爱天空丶 2024-07-23 16:12:03

您无法在枚举时更改可枚举集合,因此您必须在枚举之前或之后进行更改。

for 循环是一个不错的替代方案,但如果您的 IEnumerable 集合未实现 ICollection,则这是不可能的。

要么:

1) 首先复制集合。 枚举复制的集合并在枚举过程中更改原始集合。 (@tvanfosson)

2) 保留更改列表并在枚举后提交它们。

You can't change the enumerable collection while it is being enumerated, so you will have to make your changes before or after enumerating.

The for loop is a nice alternative, but if your IEnumerable collection does not implement ICollection, it is not possible.

Either:

1) Copy collection first. Enumerate the copied collection and change the original collection during the enumeration. (@tvanfosson)

or

2) Keep a list of changes and commit them after the enumeration.

紫轩蝶泪 2024-07-23 16:12:03

LINQ 对于处理集合非常有效。

我不清楚你的类型和结构,但我会尽我所能来适应你的例子。

从您的代码看来,对于每个项目,您都将其自己的“Enumerable”属性中的所有内容添加到该项目中。 这非常简单:

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)));

LINQ is very effective for juggling with collections.

Your types and structure are unclear to me, but I will try to fit your example to the best of my ability.

From your code it appears that, for each item, you are adding to that item everything from its own 'Enumerable' property. This is very simple:

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

As a more general example, let's say we want to iterate a collection and remove items where a certain condition is true. Avoiding foreach, using LINQ:

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

Add an item based on each existing item? No problem:

myCollection = myCollection.Concat(myCollection.Select(item => new Item(item.SomeProp)));
财迷小姐 2024-07-23 16:12:03

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

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);
        }
    }
}

Here's how you can do that (quick and dirty solution. If you really need this kind of behavior, you should either reconsider your design or override all IList<T> members and aggregate the source list):

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);
        }
    }
}
墨小墨 2024-07-23 16:12:03

要添加 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);

To add to Timo's answer LINQ can be used like this as well:

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);
蘸点软妹酱 2024-07-23 16:12:03

从性能角度来看,最好的方法可能是使用一两个阵列。 将列表复制到数组,对数组进行操作,然后从数组构建一个新列表。 访问数组元素比访问列表项更快,并且 ListT[] 之间的转换可以使用快速“批量复制”操作,从而避免访问单个项目的相关开销。

例如,假设您有一个 List 并希望列表中以 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
}

如果 List 提供一种从数组的一部分构造列表的方法,那将会很有帮助,但我不知道有任何有效的方法这样做。 尽管如此,数组上的操作还是相当快的。 值得注意的是,从列表中添加和删除项目不需要“推动”其他项目; 每个项目都直接写入数组中的适当位置。

The best approach from a performance perspective is probably to use a one or two arrays. Copy the list to an array, do operations on the array, and then build a new list from the array. Accessing an array element is faster than accessing a list item, and conversions between a List<T> and a T[] can use a fast "bulk copy" operation which avoids the overhead associated accessing individual items.

For example, suppose you have a List<string> and wish to have every string in the list which starts with T be followed by an item "Boo", while every string that starts with "U" is dropped entirely. An optimal approach would probably be something like:

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
}

It would have been helpful if List<T> provided a method to construct a list from a portion of an array, but I'm unaware of any efficient method for doing so. Still, operations on arrays are pretty fast. Of note is the fact that adding and removing items from the list does not require "pushing" around other items; each item gets written directly to its appropriate spot in the array.

南笙 2024-07-23 16:12:03

我编写了一个简单的步骤,但由于这一步骤,性能会下降,

这是我的代码片段:-

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;
                                }
                            }

I have written one easy step, but because of this performance will be degraded

Here is my code snippet:-

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;
                                }
                            }
可是我不能没有你 2024-07-23 16:12:02

foreach 中使用的集合是不可变的。 这很大程度上是设计使然。

正如 MSDN 上所述:

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

链接 表明新的并发集合中允许这样做。

The collection used in foreach is immutable. This is very much by design.

As it says on MSDN:

The foreach statement is used to
iterate through the collection to get
the information that you want, but can
not be used to add or remove items
from the source collection to avoid
unpredictable side effects. If you
need to add or remove items from the
source collection, use a for loop.

The post in the link provided by Poko indicates that this is allowed in the new concurrent collections.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文