Можно ли написать несколько итераторов для типа на С#?

Итак, для типа:

CoolCollection<T>

вы могли бы:

foreach (T item in coolCollection)
{
    ...
}

foreach (CoolNode node in coolCollection)
{
    ...
}

Если это невозможно, возможно, например, foreach2 или какой-либо другой способ повторения. Часто мне действительно хотелось бы иметь более одного способа итерации по типу.

РЕДАКТИРОВАТЬ: Извините, если это было неясно. По сути, CoolNode — это узел, который создает CoolCollection. У CoolNode есть свойство value для возврата T, но мне нужен еще один итератор, чтобы возвращать только CoolNodes.

EDIT2: я не могу сделать coolCollection.Something для итерации, потому что CoolNodes подключены через свойство Next, например LinkedList. Поэтому мне нужно реализовать 2 итератора.


person Joan Venge    schedule 14.05.2009    source источник
comment
Так в чем вопрос? Вы не можете перебирать свою коллекцию более одного раза?   -  person GalacticCowboy    schedule 15.05.2009
comment
Непонятно, что вы имеете в виду. Являются ли T и CoolNode разными подколлекциями или просто разными представлениями одной и той же коллекции? Во-первых, вы должны поступать так, как сказал J.13.L, и иметь методы или свойства, которые возвращают соответствующий Enumerable для каждого. Для второго можно использовать соответствующее приведение или преобразование.   -  person Matthew Flaschen    schedule 15.05.2009


Ответы (5)


Просто сделайте так, чтобы CoolCollection<T> явно реализовывал IEnumerable<CoolNode<T>>, а также IEnumerable<T>. (Я предполагаю, что это действительно CoolNode<T>, но если нет, просто уберите лишние <T> везде.)

Это позволит вам повторять оба способа, хотя вам понадобится приведение.

Для этого вам понадобится что-то вроде:

class CoolCollection<T> : ICollection<T>, IEnumerable<CoolNode<T>>
{
    IEnumerator<CoolNode<T>> IEnumerable<CoolNode<T>>.GetEnumerator()
    {
        ///...Do work here...
    }

    IEnumerator<T> GetEnumerator()
    {
        ///...Do work here...
    }
}

Использование этого будет выглядеть так:

foreach (T item in coolCollection)
{
    ...
}


foreach (CoolNode<T> node in (IEnumerable<CoolNode<T>>)coolCollection)
{
    ...
}

Другим вариантом было бы открыть свойство для «узлов», чтобы вы могли сделать:

foreach(var nodes in coolCollection.Nodes)
{ ... }

Чтобы реализовать это, вы должны немного изменить ситуацию. Вам нужно будет создать частный класс, реализующий перечислитель... что-то вроде:

class CoolCollection<T> : ICollection<T>
{
    private List<CoolNode<T>> nodes;

    IEnumerable<CoolNode<T>> Nodes
    {
        get 
        {
             foreach(var node in this.nodes) { yield return node; }
        }
    }
}
person Reed Copsey    schedule 14.05.2009
comment
Спасибо Рид. Зачем мне нужен гипс? Также я отредактировал вопрос, чтобы было понятно, если это поможет. - person Joan Venge; 15.05.2009
comment
Я добавил пример — вам понадобится приведение, чтобы компилятор обрабатывал coolCollection как IEnumerable‹CoolNode› вместо IEnumerable‹T› в этом случае. Я также предполагаю, что это CoolNode‹T›, если узлы являются частью общей коллекции... - person Reed Copsey; 15.05.2009
comment
@sambo99: Хороший вопрос. Я бы лично сделал второй вариант, если бы реализовывал это. Я бы реализовал свою коллекцию IEnumerable‹T› и имел свойство, реализующее IEnumerable‹CoolNode‹T›› для возврата узлов. Я думаю, что это намного чище и легче следовать. - person Reed Copsey; 15.05.2009
comment
@Reed, я получаю +1 за это: P, я разочарован тем, что мой длинный подробный ответ все еще находится на -1 - person Sam Saffron; 15.05.2009
comment
@sambo99: Конечно, через секунду я прокомментирую твой ;) - person Reed Copsey; 15.05.2009
comment
Спасибо Рид. Это похоже на то, что мне нужно. Теперь, когда я подумал о методе свойств (ваше решение № 2 имеет смысл). Не могли бы вы привести пример того, как вы это реализуете? Я не уверен, как реализовать итератор в свойстве. - person Joan Venge; 15.05.2009
comment
@Joan Venge: Теперь это там ... Вам нужно реализовать частный класс IEnumerable для этого, но его довольно легко реализовать ... - person Reed Copsey; 15.05.2009
comment
Спасибо Рид. Кроме того, почему вы создали конструктор для NodeEnumerator? Чтобы сделать его универсальным? Как вы думаете, я должен просто сделать это специально для CoolCollection? С точки зрения хорошей практики я имею в виду. - person Joan Venge; 15.05.2009
comment
@Reed @Joan, я не вижу причин создавать частный класс NodeEnumerator (если только он не предназначен для сценария повторного использования, и в этом случае он не будет частным классом), вы можете получить возврат непосредственно из ваших узлов IEnumerable. - person Sam Saffron; 15.05.2009
comment
@Joan: я обновил его, чтобы устранить необходимость в частном классе ... это значительно упрощает работу. У Sambo99 была хорошая мысль... Вариант выше - хороший, чистый способ реализовать это (сейчас). - person Reed Copsey; 15.05.2009

Если я правильно понял вопрос...

Вы можете сделать это аналогично тому, как это делают некоторые другие объекты коллекции:

Например:

foreach (int key in IDictionary.Keys)
{

}

foreach (object value in IDictionary.Values)
{

}

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

person bytebender    schedule 14.05.2009

Нет, ты не можешь этого сделать. Вы не можете перегружать свой итератор по умолчанию.

Представьте, если бы вы могли перегрузить свой итератор по умолчанию.

Что бы это сделало? foreach (object o in foo) не было бы логического способа выбрать правильный итератор.

Что вы можете сделать, так это иметь второй метод с именем ForEach2, который перебирает вашу коллекцию другим способом. Или вы можете явно реализовать интерфейс. Или вы можете использовать композицию Linq для такого рода вещей.

С точки зрения дизайна класса:

interface IBar {
   IEnumerator<string> GetEnumerator();
}

class Foo : IBar, IEnumerable<int> {

    // Very bad, risky code. Enumerator implementations, should 
    // line up in your class design. 
    public IEnumerator<int> GetEnumerator()
    {
        yield return 1;
        yield return 2;
        yield return 3;
        yield return 4;
    }

    IEnumerator<string> IBar.GetEnumerator()
    {
        yield return "hello";
    }

    // must be IEnumerable if you want to support foreach 
    public IEnumerable<string> AnotherIterator
    { 
        get {
           yield return "hello2";
        }
    }


    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator(); 
    }

}

Расширения LINQ для EachPair

struct Pair<T> { 
    public T First;
    public T Second;
}

static class LinqExtension {
    public static IEnumerable<Pair<T>> EachPair<T>(this IEnumerable<T> input) {
        T first = default(T);
        bool gotFirst = false;
        foreach (var item in input)
        {
            if (!gotFirst)
            {
                first = item;
                gotFirst = true;
            }
            else {
                yield return new Pair<T>() { First = first, Second = item };
                gotFirst = false;
            }
        } 
    }
}

Тестовый код:

class Program
{
    static void Main(string[] args)
    {
        var foo = new Foo(); 

        foreach (int number in foo)
        {
            Console.WriteLine(number);
        }

        // LINQ composition - a good trick where you want
        //  another way to iterate through the same data 
        foreach (var pair in foo.EachPair())
        {
            Console.WriteLine("got pair {0} {1}", pair.First, pair.Second);
        }

        // This is a bad and dangerous practice. 
        // All default enumerators should be the same, otherwise
        // people will get confused.
        foreach (string str in (IBar)foo)
        {
            Console.WriteLine(str);
        }

        // Another possible way, which can be used to iterate through
        //   a portion of your collection eg. Dictionary.Keys 
        foreach (string str in foo.AnotherIterator)
        {
            Console.WriteLine(str);
        }
    }
person Sam Saffron    schedule 14.05.2009
comment
Может кто-нибудь сказать мне, почему это худший ответ? - person Sam Saffron; 15.05.2009
comment
В первом блоке: IEnumerable‹string› Другой Iterator должен быть IEnumerator‹string› - person Reed Copsey; 15.05.2009
comment
Также: каждая пара не кажется актуальной? Какова его цель здесь? Он перечисляет и строит пары (элемент 0 и 1, затем 1 и 2, 2 и 3,...). Кажется, не совпадает с другой информацией.... - person Reed Copsey; 15.05.2009
comment
@Reed, в вопросе @Joan упоминается «может быть, как foreach2», поэтому я продемонстрировал общий способ реализации foreach2. - person Sam Saffron; 15.05.2009
comment
@ sambo99: я понял, что foreach2 - это второй foreach, а не то, что повторяется парами. Теперь твой ответ имеет смысл, но я не уверен, что она это имела в виду. ;) - person Reed Copsey; 15.05.2009
comment
@Reed, из FDG Не используйте IEnumerator‹T› ... кроме как в качестве возвращаемого типа метода GetEnumerator ... - person Sam Saffron; 15.05.2009
comment
Спасибо самбо, добавил +1 за подробный ответ. - person Joan Venge; 15.05.2009

Если CoolCollection реализует IEnumerable, вы можете написать:

foreach (var item in coolCollection)
{
    ...
}

или если T является CoolNode

foreach (CoolNode node in coolCollection)
{
    ...
}

Если вам нужно каким-то образом преобразовать каждый элемент в свой тип, вы можете использовать оператор Linq Select:

foreach (CoolNode node in coolCollection.Select(item => ConvertToNode(item))
{
    ...
}
person bbmud    schedule 14.05.2009

Взгляните на фрагмент iterindex. В своем классе введите iterindex и нажмите [TAB]. Это поможет вам реализовать шаблон «Именованный итератор и индексатор».

Результат можно использовать следующим образом:

foreach (var e in myTree.DepthFirstView) // supports iteration
{
    if (e == myTree.DepthFirstView[2]) // supports indexing
    {
        // ...
    }
}

(Я написал этот фрагмент, но подозреваю, что он так и не был использован с пользой. Никогда.)

person Jay Bazuzi    schedule 14.05.2009