Как реализовать IEnumerable <T>

124

Я знаю, как реализовать не общий IEnumerable, например:

using System;
using System.Collections;

namespace ConsoleApplication33
{
    class Program
    {
        static void Main(string[] args)
        {
            MyObjects myObjects = new MyObjects();
            myObjects[0] = new MyObject() { Foo = "Hello", Bar = 1 };
            myObjects[1] = new MyObject() { Foo = "World", Bar = 2 };

            foreach (MyObject x in myObjects)
            {
                Console.WriteLine(x.Foo);
                Console.WriteLine(x.Bar);
            }

            Console.ReadLine();
        }
    }

    class MyObject
    {
        public string Foo { get; set; }
        public int Bar { get; set; }
    }

    class MyObjects : IEnumerable
    {
        ArrayList mylist = new ArrayList();

        public MyObject this[int index]
        {
            get { return (MyObject)mylist[index]; }
            set { mylist.Insert(index, value); }
        }

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

Однако я также заметил, что IEnumerable имеет общую версию IEnumerable<T>, но я не могу понять, как ее реализовать.

Если я добавлю using System.Collections.Generic;к своим директивам using, а затем изменю:

class MyObjects : IEnumerable

чтобы:

class MyObjects : IEnumerable<MyObject>

Затем щелкните правой кнопкой мыши IEnumerable<MyObject>и выберите Implement Interface => Implement Interface, Visual Studio услужливо добавляет следующий блок кода:

IEnumerator<MyObject> IEnumerable<MyObject>.GetEnumerator()
{
    throw new NotImplementedException();
}

Возврат неуниверсального объекта IEnumerable из GetEnumerator();метода на этот раз не сработает, так что мне здесь указать? Интерфейс командной строки теперь игнорирует неуниверсальную реализацию и направляется прямо к общей версии, когда пытается выполнить перечисление через мой массив во время цикла foreach.

JMK
источник

Ответы:

149

Если вы решите использовать универсальную коллекцию, например List<MyObject>вместо ArrayList, вы обнаружите, что List<MyObject>будет предоставлять как общие, так и неуниверсальные перечислители, которые вы можете использовать.

using System.Collections;

class MyObjects : IEnumerable<MyObject>
{
    List<MyObject> mylist = new List<MyObject>();

    public MyObject this[int index]  
    {  
        get { return mylist[index]; }  
        set { mylist.Insert(index, value); }  
    } 

    public IEnumerator<MyObject> GetEnumerator()
    {
        return mylist.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}
Монро Томас
источник
1
Есть ли разница между возвратом this.GetEnumerator()и простым возвратом в неуниверсальной реализации GetEnumerator()?
Tanner Swett,
1
@TannerSwett Нет никакой разницы.
Монро Томас
Вы также можете унаследовать от него, Collection<MyObject>и тогда вам не придется писать какой-либо собственный код.
Джон Алексиу
@ ja72 Что делать, если вы уже наследуете от другого базового класса и не можете наследовать от Collection <MyObject>?
Monroe Thomas
1
@MonroeThomas - тогда вам нужно обернуть List<T>и реализовать, IEnumerable<T>как показано в вашем ответе.
Джон Алексиу
69

Вероятно, вам не нужна явная реализация IEnumerable<T>(что вы показали).

Обычный шаблон - использовать IEnumerable<T>'s GetEnumeratorв явной реализации IEnumerable:

class FooCollection : IEnumerable<Foo>, IEnumerable
{
    SomeCollection<Foo> foos;

    // Explicit for IEnumerable because weakly typed collections are Bad
    System.Collections.IEnumerator IEnumerable.GetEnumerator()
    {
        // uses the strongly typed IEnumerable<T> implementation
        return this.GetEnumerator();
    }

    // Normal implementation for IEnumerable<T>
    IEnumerator<Foo> GetEnumerator()
    {
        foreach (Foo foo in this.foos)
        {
            yield return foo;
            //nb: if SomeCollection is not strongly-typed use a cast:
            // yield return (Foo)foo;
            // Or better yet, switch to an internal collection which is
            // strongly-typed. Such as List<T> or T[], your choice.
        }

        // or, as pointed out: return this.foos.GetEnumerator();
    }
}
user7116
источник
2
Это хорошее решение, если SomeCollection <T> еще не реализует IEnumerable <T>, или если вам нужно преобразовать или выполнить другую обработку каждого элемента, прежде чем он будет возвращен в последовательности перечисления. Если ни одно из этих условий не выполняется, вероятно, более эффективно просто вернуть this.foos.GetEnumerator ();
Monroe Thomas
@MonroeThomas: согласен. Не знаю, какую коллекцию использует OP.
user7116 02
8
Хорошая точка зрения. Но реализация IEnumerableизбыточна ( IEnumerable<T>уже наследуется от нее).
rsenna
@rsenna, это правда, однако имейте в виду, что даже интерфейсы .NET, такие как IList, избыточно реализуют интерфейсы, это для удобства чтения - открытый интерфейс IList: ICollection, IEnumerable
Дэвид Клемпфнер
@Backwards_Dave Я на самом деле (до сих пор) думаю, что он делает его менее читаемым, поскольку добавляет ненужный шум, но я понимаю, что вы сказали, и считаю, что это верное мнение.
rsenna
24

Почему вы делаете это вручную? yield returnавтоматизирует весь процесс обработки итераторов. (Я также писал об этом в своем блоге , включая просмотр кода, созданного компилятором).

Если вы действительно хотите сделать это самостоятельно, вам также нужно вернуть универсальный перечислитель. Вы не сможете больше использовать, ArrayListпоскольку это не общий. List<MyObject>Вместо этого замените его на . Это, конечно, предполагает, что MyObjectв вашей коллекции есть только объекты типа (или производных типов).

Андерс Абель
источник
2
+1, следует отметить, что предпочтительным шаблоном является реализация универсального интерфейса и явная реализация неуниверсального интерфейса. Возврат доходности - наиболее естественное решение для универсального интерфейса.
user7116 02
5
Если OP не будет выполнять некоторую обработку в перечислителе, использование yield return просто добавляет накладные расходы другого конечного автомата. OP должен просто возвращать перечислитель, предоставленный базовой общей коллекцией.
Monroe Thomas
@MonroeThomas: Ты прав. Я не очень внимательно прочитал вопрос, прежде чем писать о нем yield return. Я ошибочно предположил, что была некоторая нестандартная обработка.
Anders Abel
4

Если вы работаете с универсальными шаблонами, используйте List вместо ArrayList. В List есть именно тот метод GetEnumerator, который вам нужен.

List<MyObject> myList = new List<MyObject>();
Амирам Корах
источник
0

превратить мой список в List<MyObject>, это один из вариантов

ColWhi
источник
0

Обратите внимание, что все IEnumerable<T>уже реализовано, System.Collectionsпоэтому другой подход заключается в наследовании вашего MyObjectsкласса System.Collectionsкак базового класса ( документация ):

System.Collections: предоставляет базовый класс для общей коллекции.

Мы можем позже сделать наши собственные реализации внешним переопределить виртуальные System.Collectionsметоды , чтобы обеспечить пользовательское поведение (только для ClearItems, InsertItem, RemoveItemи SetItemвместе с Equals, GetHashCodeи ToStringс Object). В отличие от, List<T>который не предназначен для легкого расширения.

Пример:

public class FooCollection : System.Collections<Foo>
{
    //...
    protected override void InsertItem(int index, Foo newItem)
    {
        base.InsertItem(index, newItem);     
        Console.Write("An item was successfully inserted to MyCollection!");
    }
}

public static void Main()
{
    FooCollection fooCollection = new FooCollection();
    fooCollection.Add(new Foo()); //OUTPUT: An item was successfully inserted to FooCollection!
}

Обратите внимание, что движение от collectionрекомендовано только в том случае, если требуется настраиваемое поведение сбора, что случается редко. см. использование .

Шахар Шокрани
источник