C# 中排列枚举的组合迭代?

问题描述 投票:0回答:6

有没有办法对 C# 中的排列枚举进行

foreach
样式迭代?对于可下标列表,我知道可以使用常规的
for
循环在索引范围内迭代 int,但出于多种原因,我真的更喜欢
foreach
而不是
for

如果它能在 C# 2.0 中运行,还有加分。

澄清:我说的是多重排列的枚举。就像一排排的铅笔和纸。我希望每支铅笔都能在相应的纸上写字。

注意: 最初这个问题使用术语 parallel 而不是 lined up

c# loops foreach iteration enumerable
6个回答
10
投票

.NET 4 的 BlockingCollection 使这变得非常简单。创建一个BlockingCollection,在可枚举方法中返回其.GetConsumingEnumerable()。然后 foreach 只是添加到阻塞集合中。

例如

private BlockingCollection<T> m_data = new BlockingCollection<T>();

public IEnumerable<T> GetData( IEnumerable<IEnumerable<T>> sources )
{
    Task.Factory.StartNew( () => ParallelGetData( sources ) );
    return m_data.GetConsumingEnumerable();
}

private void ParallelGetData( IEnumerable<IEnumerable<T>> sources )
{
    foreach( var source in sources )
    {
        foreach( var item in source )
        {
            m_data.Add( item );
        };
    }

    //Adding complete, the enumeration can stop now
    m_data.CompleteAdding();
}

希望这有帮助。 顺便说一句,我昨晚发布了关于此的博客

安德烈


9
投票
简短的回答,不。

foreach

 一次仅适用于一个可枚举。

但是,如果将并行可枚举性合并为一个,则可以

foreach

 合并后的可枚举性。我不知道有任何简单的内置方法可以做到这一点,但以下应该可以工作(尽管我还没有测试过):

public IEnumerable<TSource[]> Combine<TSource>(params object[] sources) { foreach(var o in sources) { // Choose your own exception if(!(o is IEnumerable<TSource>)) throw new Exception(); } var enums = sources.Select(s => ((IEnumerable<TSource>)s).GetEnumerator()) .ToArray(); while(enums.All(e => e.MoveNext())) { yield return enums.Select(e => e.Current).ToArray(); } }

然后你可以

foreach

返回的枚举:

foreach(var v in Combine(en1, en2, en3)) { // Remembering that v is an array of the type contained in en1, // en2 and en3. }
    

3
投票
Zooba 的答案很好,但您可能还想查看

“如何同时迭代两个数组”的答案


3
投票
我从 .NET4 并行库编写了 EachParallel() 的实现。它与 .NET 3.5 兼容:

C# 3.5 中的并行 ForEach 循环 用途:

string[] names = { "cartman", "stan", "kenny", "kyle" }; names.EachParallel(name => { try { Console.WriteLine(name); } catch { /* handle exception */ } });

实施:

/// <summary> /// Enumerates through each item in a list in parallel /// </summary> public static void EachParallel<T>(this IEnumerable<T> list, Action<T> action) { // enumerate the list so it can't change during execution list = list.ToArray(); var count = list.Count(); if (count == 0) { return; } else if (count == 1) { // if there's only one element, just execute it action(list.First()); } else { // Launch each method in it's own thread const int MaxHandles = 64; for (var offset = 0; offset < list.Count() / MaxHandles; offset++) { // break up the list into 64-item chunks because of a limitiation // in WaitHandle var chunk = list.Skip(offset * MaxHandles).Take(MaxHandles); // Initialize the reset events to keep track of completed threads var resetEvents = new ManualResetEvent[chunk.Count()]; // spawn a thread for each item in the chunk int i = 0; foreach (var item in chunk) { resetEvents[i] = new ManualResetEvent(false); ThreadPool.QueueUserWorkItem(new WaitCallback((object data) => { int methodIndex = (int)((object[])data)[0]; // Execute the method and pass in the enumerated item action((T)((object[])data)[1]); // Tell the calling thread that we're done resetEvents[methodIndex].Set(); }), new object[] { i, item }); i++; } // Wait for all threads to execute WaitHandle.WaitAll(resetEvents); } } }
    

1
投票
如果您想坚持基础知识 - 我以更简单的方式重写了当前接受的答案:

public static IEnumerable<TSource[]> Combine<TSource> (this IEnumerable<IEnumerable<TSource>> sources) { var enums = sources .Select (s => s.GetEnumerator ()) .ToArray (); while (enums.All (e => e.MoveNext ())) { yield return enums.Select (e => e.Current).ToArray (); } } public static IEnumerable<TSource[]> Combine<TSource> (params IEnumerable<TSource>[] sources) { return sources.Combine (); }
    

0
投票
这对你有用吗?

public static class Parallel { public static void ForEach<T>(IEnumerable<T>[] sources, Action<T> action) { foreach (var enumerable in sources) { ThreadPool.QueueUserWorkItem(source => { foreach (var item in (IEnumerable<T>)source) action(item); }, enumerable); } } } // sample usage: static void Main() { string[] s1 = { "1", "2", "3" }; string[] s2 = { "4", "5", "6" }; IEnumerable<string>[] sources = { s1, s2 }; Parallel.ForEach(sources, s => Console.WriteLine(s)); Thread.Sleep(0); // allow background threads to work }

对于 C# 2.0,您需要将上面的 lambda 表达式转换为委托。

注意:此实用方法使用后台线程。您可能想要修改它以使用前台线程,并且可能需要等到所有线程完成。如果您这样做,我建议您创建

sources.Length - 1

 线程,并将当前执行线程用于最后一个(或第一个)源。

(我希望我可以在代码中包含等待线程完成,但很抱歉我还不知道该怎么做。我想你应该使用

a WaitHandle

 Thread.Join()
。)

© www.soinside.com 2019 - 2024. All rights reserved.