C# 是否有将 IEnumerator 转换为 IEnumerable 的内置方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1029612/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 05:55:53  来源:igfitidea点击:

Is there a built-in way to convert IEnumerator to IEnumerable

c#.netlinq

提问by Sam Saffron

Is there a built-in way to convert IEnumerator<T>to IEnumerable<T>?

是否有内置的转换IEnumerator<T>方式IEnumerable<T>

采纳答案by JaredPar

You could use the following which will kindawork.

你可以使用,这将在下面还挺工作。

public class FakeEnumerable<T> : IEnumerable<T> {
  private IEnumerator<T> m_enumerator;
  public FakeEnumerable(IEnumerator<T> e) {
    m_enumerator = e;
  }
  public IEnumerator<T> GetEnumerator() { 
    return m_enumerator;
  }
  // Rest omitted 
}

This will get you into trouble though when people expect successive calls to GetEnumerator to return different enumerators vs. the same one. But if it's a one time only use in a veryconstrained scenario, this could unblock you.

尽管当人们期望连续调用 GetEnumerator 返回不同的枚举器与相同的枚举器时,这会给您带来麻烦。但是,如果它仅在非常受限的情况下使用一次,则这可能会阻止您。

I do suggest though you try and not do this because I think eventually it will come back to haunt you.

我确实建议您尝试不要这样做,因为我认为最终它会再次困扰您。

A safer option is along the lines Jonathan suggested. You can expend the enumerator and create a List<T>of the remaining items.

一个更安全的选择是 Jonathan 建议的路线。您可以使用枚举器并创建List<T>剩余项目中的一个。

public static List<T> SaveRest<T>(this IEnumerator<T> e) {
  var list = new List<T>();
  while ( e.MoveNext() ) {
    list.Add(e.Current);
  }
  return list;
}

回答by Jason Watts

Nope, IEnumerator<> and IEnumerable<> are different beasts entirely.

不,IEnumerator<> 和 IEnumerable<> 是完全不同的野兽。

回答by Jonathan Rupp

As Jason Watts said -- no, not directly.

正如 Jason Watts 所说 - 不,不是直接的。

If you really want to, you could loop through the IEnumerator<T>, putting the items into a List<T>, and return that, but I'm guessing that's not what you're looking to do.

如果你真的想要,你可以遍历 IEnumerator<T>,将项目放入 List<T>,然后返回,但我猜这不是你想要做的。

The basic reason you can't go that direction (IEnumerator<T> to a IEnumerable<T>) is that IEnumerable<T> represents a set that can be enumerated, but IEnumerator<T> is a specific enumeratation over a set of items -- you can't turn the specific instance back into the thing that created it.

您不能朝那个方向(IEnumerator<T> 到 IEnumerable<T>)的基本原因是 IEnumerable<T> 表示可以枚举的集合,但 IEnumerator<T> 是对一组项目的特定枚举-- 您无法将特定实例转回创建它的事物。

回答by Rune FS

The easiest way of converting I can think of is via the yield statement

我能想到的最简单的转换方法是通过 yield 语句

public static IEnumerable<T> ToIEnumerable<T>(this IEnumerator<T> enumerator) {
  while ( enumerator.MoveNext() ) {
    yield return enumerator.Current;
  }
}

compared to the list version this has the advantage of not enumerating the entire list before returning an IEnumerable. using the yield statement you'd only iterate over the items you need, whereas using the list version, you'd first iterate over all items in the list and then all the items you need.

与列表版本相比,它的优点是在返回 IEnumerable 之前不枚举整个列表。使用 yield 语句,您只能迭代您需要的项目,而使用列表版本,您将首先迭代列表中的所有项目,然后是您需要的所有项目。

for a little more fun you could change it to

为了更有趣,您可以将其更改为

public static IEnumerable<K> Select<K,T>(this IEnumerator<T> e, 
                                         Func<K,T> selector) {
      while ( e.MoveNext() ) {
        yield return selector(e.Current);
      }
    }

you'd then be able to use linq on your enumerator like:

然后您就可以在您的枚举器上使用 linq,例如:

IEnumerator<T> enumerator;
var someList = from item in enumerator
               select new classThatTakesTInConstructor(item);

回答by sehe

EnumeratorEnumerable<T>

EnumeratorEnumerable<T>

A threadsafe, resettable adaptor from IEnumerator<T>to IEnumerable<T>

一个线程安全的、可重置的适配器从IEnumerator<T>IEnumerable<T>

I use Enumerator parameters like in C++ forward_iterator concept.

我在 C++ forward_iterator 概念中使用枚举器参数。

I agree that this can lead to confusion as too many people will indeed assume Enumerators are /like/ Enumerables, but they are not.

我同意这会导致混淆,因为太多人确实会认为枚举数是 /like/ 枚举数,但事实并非如此。

However, the confusion is fed by the fact that IEnumerator contains the Reset method. Here is my idea of the most correct implementation. It leverages the implementation of IEnumerator.Reset()

然而,混淆是由 IEnumerator 包含 Reset 方法这一事实造成的。这是我对最正确实现的想法。它利用了 IEnumerator.Reset() 的实现

A major difference between an Enumerable and and Enumerator is, that an Enumerable might be able to create several Enumerators simultaneously. This implementation puts a whole lot of work into making sure that this never happens for the EnumeratorEnumerable<T>type. There are two EnumeratorEnumerableModes:

Enumerable 和 Enumerator 之间的主要区别在于,一个 Enumerable 可能能够同时创建多个 Enumerator。这个实现投入了大量的工作来确保这种情况永远不会发生在EnumeratorEnumerable<T>类型上。有两个EnumeratorEnumerableMode

  • Blocking(meaning that a second caller will simply wait till the first enumeration is completed)
  • NonBlocking(meaning that a second (concurrent) request for an enumerator simply throws an exception)
  • Blocking(意味着第二个调用者将等待第一个枚举完成)
  • NonBlocking(意味着对枚举器的第二个(并发)请求只会引发异常)

Note 1:74 lines are implementation, 79 lines are testing code :)

1:74 行是实现,79 行是测试代码 :)

Note 2:I didn't refer to any unit testing framework for SO convenience

注 2:为了方便起见,我没有提到任何单元测试框架

using System;
using System.Diagnostics;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

namespace EnumeratorTests
{
    public enum EnumeratorEnumerableMode
    {
        NonBlocking,
        Blocking,
    }

    public sealed class EnumeratorEnumerable<T> : IEnumerable<T>
    {
        #region LockingEnumWrapper

        public sealed class LockingEnumWrapper : IEnumerator<T>
        {
            private static readonly HashSet<IEnumerator<T>> BusyTable = new HashSet<IEnumerator<T>>();
            private readonly IEnumerator<T> _wrap;

            internal LockingEnumWrapper(IEnumerator<T> wrap, EnumeratorEnumerableMode allowBlocking) 
            {
                _wrap = wrap;

                if (allowBlocking == EnumeratorEnumerableMode.Blocking)
                    Monitor.Enter(_wrap);
                else if (!Monitor.TryEnter(_wrap))
                    throw new InvalidOperationException("Thread conflict accessing busy Enumerator") {Source = "LockingEnumWrapper"};

                lock (BusyTable)
                {
                    if (BusyTable.Contains(_wrap))
                        throw new LockRecursionException("Self lock (deadlock) conflict accessing busy Enumerator") { Source = "LockingEnumWrapper" };
                    BusyTable.Add(_wrap);
                }

                // always implicit Reset
                _wrap.Reset();
            }

            #region Implementation of IDisposable and IEnumerator

            public void Dispose()
            {
                lock (BusyTable)
                    BusyTable.Remove(_wrap);

                Monitor.Exit(_wrap);
            }
            public bool MoveNext()      { return _wrap.MoveNext(); }
            public void Reset()         { _wrap.Reset(); }
            public T Current            { get { return _wrap.Current; } }
            object IEnumerator.Current  { get { return Current; } }

            #endregion
        }

        #endregion

        private readonly IEnumerator<T> _enumerator;
        private readonly EnumeratorEnumerableMode _allowBlocking;

        public EnumeratorEnumerable(IEnumerator<T> e, EnumeratorEnumerableMode allowBlocking)
        {
            _enumerator = e;
            _allowBlocking = allowBlocking;
        }

        private LockRecursionPolicy a;
        public IEnumerator<T> GetEnumerator()
        {
            return new LockingEnumWrapper(_enumerator, _allowBlocking);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    class TestClass
    {
        private static readonly string World = "hello world\n";

        public static void Main(string[] args)
        {
            var master = World.GetEnumerator();
            var nonblocking = new EnumeratorEnumerable<char>(master, EnumeratorEnumerableMode.NonBlocking);
            var blocking    = new EnumeratorEnumerable<char>(master, EnumeratorEnumerableMode.Blocking);

            foreach (var c in nonblocking)  Console.Write(c); // OK (implicit Reset())
            foreach (var c in blocking)     Console.Write(c); // OK (implicit Reset())
            foreach (var c in nonblocking)  Console.Write(c); // OK (implicit Reset())
            foreach (var c in blocking)     Console.Write(c); // OK (implicit Reset())

            try
            {
                var willRaiseException = from c1 in nonblocking from c2 in nonblocking select new {c1, c2};
                Console.WriteLine("Cartesian product: {0}", willRaiseException.Count()); // RAISE
            }
            catch (Exception e) { Console.WriteLine(e); }

            foreach (var c in nonblocking)  Console.Write(c); // OK (implicit Reset())
            foreach (var c in blocking)     Console.Write(c); // OK (implicit Reset())

            try
            {
                var willSelfLock = from c1 in blocking from c2 in blocking select new { c1, c2 };
                Console.WriteLine("Cartesian product: {0}", willSelfLock.Count()); // LOCK
            }
            catch (Exception e) { Console.WriteLine(e); }

            // should not externally throw (exceptions on other threads reported to console)
            if (ThreadConflictCombinations(blocking, nonblocking))
                throw new InvalidOperationException("Should have thrown an exception on background thread");
            if (ThreadConflictCombinations(nonblocking, nonblocking))
                throw new InvalidOperationException("Should have thrown an exception on background thread");

            if (ThreadConflictCombinations(nonblocking, blocking))
                Console.WriteLine("Background thread timed out");
            if (ThreadConflictCombinations(blocking, blocking))
                Console.WriteLine("Background thread timed out");

            Debug.Assert(true); // Must be reached
        }

        private static bool ThreadConflictCombinations(IEnumerable<char> main, IEnumerable<char> other)
        {
            try
            {
                using (main.GetEnumerator())
                {
                    var bg = new Thread(o =>
                        {
                            try { other.GetEnumerator(); }
                            catch (Exception e) { Report(e); }
                        }) { Name = "background" };
                    bg.Start();

                    bool timedOut = !bg.Join(1000); // observe the thread waiting a full second for a lock (or throw the exception for nonblocking)

                    if (timedOut)
                        bg.Abort();

                    return timedOut;
                }
            } catch
            {
                throw new InvalidProgramException("Cannot be reached");
            }
        }

        static private readonly object ConsoleSynch = new Object();
        private static void Report(Exception e)
        {
            lock (ConsoleSynch)
                Console.WriteLine("Thread:{0}\tException:{1}", Thread.CurrentThread.Name, e);
        }
    }
}

Note 3:I think the implementation of the thread locking (especially around BusyTable) is quite ugly; However, I didn't want to resort to ReaderWriterLock(LockRecursionPolicy.NoRecursion)and didn't want to assume .Net 4.0 for SpinLock

注3:我认为线程锁定(尤其是围绕BusyTable)的实现相当丑陋;但是,我不想诉诸于ReaderWriterLock(LockRecursionPolicy.NoRecursion)也不想假设 .Net 4.0SpinLock

回答by Carlo V. Dango

static class Helper
{
  public static List<T> SaveRest<T>(this IEnumerator<T> enumerator)
  {
    var list = new List<T>();
    while (enumerator.MoveNext())
    {
      list.Add(enumerator.Current);
    }
    return list;
  }
  public static ArrayList SaveRest(this IEnumerator enumerator)
  {
    var list = new ArrayList();
    while (enumerator.MoveNext())
    {
      list.Add(enumerator.Current);
    }
    return list;
  }
}

回答by xanatos

This is a variant I have written... The specific is a little different. I wanted to do a MoveNext()on an IEnumerable<T>, check the result, and then roll everything in a new IEnumerator<T>that was "complete" (so that included even the element of the IEnumerable<T>I had already extracted)

这是我写的一个变种……具体的有点不同。我想MoveNext()在 an上做一个IEnumerable<T>,检查结果,然后将所有内容都滚动到一个IEnumerator<T>“完整”的新元素中(这样甚至包括IEnumerable<T>我已经提取的元素)

// Simple IEnumerable<T> that "uses" an IEnumerator<T> that has
// already received a MoveNext(). "eats" the first MoveNext() 
// received, then continues normally. For shortness, both IEnumerable<T>
// and IEnumerator<T> are implemented by the same class. Note that if a
// second call to GetEnumerator() is done, the "real" IEnumerator<T> will
// be returned, not this proxy implementation.
public class EnumerableFromStartedEnumerator<T> : IEnumerable<T>, IEnumerator<T>
{
    public readonly IEnumerator<T> Enumerator;

    public readonly IEnumerable<T> Enumerable;

    // Received by creator. Return value of MoveNext() done by caller
    protected bool FirstMoveNextSuccessful { get; set; }

    // The Enumerator can be "used" only once, then a new enumerator
    // can be requested by Enumerable.GetEnumerator() 
    // (default = false)
    protected bool Used { get; set; }

    // The first MoveNext() has been already done (default = false)
    protected bool DoneMoveNext { get; set; }

    public EnumerableFromStartedEnumerator(IEnumerator<T> enumerator, bool firstMoveNextSuccessful, IEnumerable<T> enumerable)
    {
        Enumerator = enumerator;
        FirstMoveNextSuccessful = firstMoveNextSuccessful;
        Enumerable = enumerable;
    }

    public IEnumerator<T> GetEnumerator()
    {
        if (Used)
        {
            return Enumerable.GetEnumerator();
        }

        Used = true;
        return this;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public T Current
    {
        get
        {
            // There are various school of though on what should
            // happens if called before the first MoveNext() or
            // after a MoveNext() returns false. We follow the 
            // "return default(TInner)" school of thought for the
            // before first MoveNext() and the "whatever the 
            // Enumerator wants" for the after a MoveNext() returns
            // false
            if (!DoneMoveNext)
            {
                return default(T);
            }

            return Enumerator.Current;
        }
    }

    public void Dispose()
    {
        Enumerator.Dispose();
    }

    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }

    public bool MoveNext()
    {
        if (!DoneMoveNext)
        {
            DoneMoveNext = true;
            return FirstMoveNextSuccessful;
        }

        return Enumerator.MoveNext();
    }

    public void Reset()
    {
        // This will 99% throw :-) Not our problem.
        Enumerator.Reset();

        // So it is improbable we will arrive here
        DoneMoveNext = true;
    }
}

Use:

用:

var enumerable = someCollection<T>;

var enumerator = enumerable.GetEnumerator();
bool res = enumerator.MoveNext();
// do whatever you want with res/enumerator.Current

var enumerable2 = new EnumerableFromStartedEnumerator<T>(enumerator, res, enumerable);

Now, the first GetEnumerator()that will be requested to enumerable2will be given through the enumerator enumerator. From the second onward the enumerable.GetEnumerator()will be used.

现在,将通过 enumerator 给出GetEnumerator()将被请求的第一个。从第二个开始,将使用 。enumerable2enumeratorenumerable.GetEnumerator()

回答by Jim Balter

The other answers here are ... strange. IEnumerable<T>has just one method, GetEnumerator(). And an IEnumerable<T>must implement IEnumerable, which also has just one method, GetEnumerator()(the difference being that one is generic on Tand the other is not). So it should be clear how to turn an IEnumerator<T>into an IEnumerable<T>:

这里的其他答案是......奇怪。IEnumerable<T>只有一种方法,GetEnumerator(). 还有一个IEnumerable<T>must implement IEnumerable,它也只有一种方法,GetEnumerator()(区别在于一个是通用的,T另一个不是)。所以应该清楚如何将 anIEnumerator<T>变成 an IEnumerable<T>

    // using modern expression-body syntax
    public class IEnumeratorToIEnumerable<T> : IEnumerable<T>
    {
        private readonly IEnumerator<T> Enumerator;

        public IEnumeratorToIEnumerable(IEnumerator<T> enumerator) =>
            Enumerator = enumerator;

        public IEnumerator<T> GetEnumerator() => Enumerator;
        IEnumerator IEnumerable.GetEnumerator() => Enumerator;
    }

    foreach (var foo in new IEnumeratorToIEnumerable<Foo>(fooEnumerator))
        DoSomethingWith(foo);

    // and you can also do:

    var fooEnumerable = new IEnumeratorToIEnumerable<Foo>(fooEnumerator);

    foreach (var foo in fooEnumerable)
        DoSomethingWith(foo);

    // Some IEnumerators automatically repeat after MoveNext() returns false,
    // in which case this is a no-op, but generally it's required.
    fooEnumerator.Reset();

    foreach (var foo in fooEnumerable)
        DoSomethingElseWith(foo);

However, none of this should be needed because it's unusual to have an IEnumerator<T>that doesn't come with an IEnumerable<T>that returns an instance of it from its GetEnumeratormethod. If you're writing your own IEnumerator<T>, you should certainly provide the IEnumerable<T>. And really it's the other way around ... an IEnumerator<T>is intended to be a private class that iterates over instances of a public class that implements IEnumerable<T>.

但是,这一切都不是必需的,因为IEnumerator<T>不附带IEnumerable<T>从其GetEnumerator方法返回它的实例的是不寻常的。如果您正在编写自己IEnumerator<T>IEnumerable<T>. 实际上,情况IEnumerator<T>正好相反…… an旨在成为一个私有类,它遍历实现IEnumerable<T>.