IEnumerable от IEnumerator - PullRequest
       0

IEnumerable от IEnumerator

10 голосов
/ 22 января 2011

я написал про кастом IEnumerator. Какой самый простой способ сделать из него IEnumerable? Идеальным решением (одна строка кода) было бы наличие некоторого класса для этой цели. Или я должен создать свой собственный?

Ответы [ 5 ]

14 голосов
/ 22 января 2011

К сожалению, встроенного метода нет.У меня есть этот метод расширения, который я использую достаточно часто:

static IEnumerable Iterate(this IEnumerator iterator)
{
while (iterator.MoveNext())
    yield return iterator.Current;
}
5 голосов
/ 05 июня 2014

В моей коллекции утилит C # у меня есть это:

class Enumerable<T> : IEnumerable<T>
{
    Func<IEnumerator<T>> factory;
    public Enumerable(Func<IEnumerator<T>> factory) { this.factory = factory; }
    public IEnumerator<T> GetEnumerator() { return this.factory(); }
    IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
}

Для этого требуется фабричная функция IEnumerator , которая обычно может быть предоставлена ​​очень легко вместо одного экземпляра IEnumerator (который дает неправильные результаты после первой итерации и нарушает семантику IEnumerable). Это позволяет избежать проблем, отмеченных Марком Гравеллом, и устанавливает полное поведение IEnumerable.

Я использую это так:

IEnumerable<Fruit> GetFruits()
{
    var arg1 = ...
    return new Enumerable<Fruit>(() => new FruitIterator(arg1, arg2, ...));
}
5 голосов
/ 22 января 2011

Я бы действительно подошел к этому наоборот; в то время как вы можете (согласно превосходному ответу Майка П) обернуть перечислитель, притворяясь перечислимым, есть некоторые вещи, которые вы действительно не можете сделать - например, это в надежде (хотя, если честно, не настаивал), что вы можете получить несколько перечислителей из перечислимого, идеально изолированного и повторяемого. Так что если я сделаю:

Assert.AreEqual(sequence.Sum(), sequence.Sum());

но если вы "подделаете" перечислитель в перечислимое, вторая последовательность будет пустой. Или, если вы делаете их параллельно - просто странно. И есть методы , которые обрабатывают их параллельно - рассмотрим:

Assert.IsTrue(sequence.SequenceEqual(sequence));

это работает оба перечислителя вперед одновременно , поэтому, если у вас только есть один перечислитель, вы довольно огорчены.

Там - это сброс счетчиков, но это в значительной степени ошибка проектирования и не должна использоваться (это даже формальное требование в спецификации, чтобы блоки итератора вызывали исключение, если вы его вызываете) .

Лучшим (IMO) вопросом является «как мне получить перечислитель из перечислимого», и в этом случае ответом является «вызов GetEnumerator (), и не забудьте поставить галочку, чтобы избавиться от итератора» - или, проще говоря, использовать foreach».

2 голосов
/ 22 января 2011

Довольно просто:

class Enumerate : IEnumerable
{
    private Enumerate IEnumerator it;
    public Enumerate(IEnumerator it) { this.it = it; }
    public IEnumerator GetEnumerator() { return this.it; }
}

Это также позволяет пользователю вызывать IEnumerator.Reset(), если предоставленный вами счетчик поддерживает его.

0 голосов
/ 07 июня 2017

Что я делаю, так это создаю класс, который реализует IEnumerator и IEnumerable. Сделайте так, чтобы GetEnumerator () возвращал себя, и вы можете повторить его как обычно.

public class MyClassEnumerator : IEnumerator<MyClass>, IEnumerable<MyClass>
{
    public MyClass Current { get; private set; }

    object IEnumerator.Current => Current;

    public void Reset()
    {
        ...
    }

    public bool MoveNext()
    {
        ...
    }

    public IEnumerator<MyClass> GetEnumerator()
    {
        return this;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this;
    }

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