Як я реалізую 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;до своїх директив використання, а потім змініть:

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();методу цього разу не працює, тож що я тут кладу? Тепер CLI ігнорує не загальну реалізацію і прямує до загальної версії, коли вона намагається перерахувати через мій масив під час циклу foreach.

Відповіді:


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()?
Таннер Світт

1
@TannerSwett Різниці немає.
Монро Томас

Ви також можете успадкувати Collection<MyObject>і тоді вам не доведеться писати будь-який спеціальний код.
Джон Алексіу

@ ja72 Що робити, якщо ви вже успадковуєте інший базовий клас і не можете успадкувати його з колекції <MyObject>?
Монро Томас

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();
    }
}

2
Це хороше рішення, якщо SomeCollection <T> вже не реалізує IEnumerable <T> або якщо вам потрібно перетворити або виконати іншу обробку кожного елемента перед тим, як він буде повернутий у послідовності перерахування. Якщо жодне з цих умов не відповідає дійсності, то, ймовірно, більш ефективно просто повернути this.foos.GetEnumerator ();
Монро Томас

@MonroeThomas: погодився. Немає поняття, яку колекцію використовує ОП.
user7116

8
Гарна думка. Але реалізація IEnumerableє надмірною ( IEnumerable<T>від неї вже успадковується).
rsenna

@rsenna це правда, але майте на увазі навіть .NET інтерфейси, такі як IList, надмірно реалізують інтерфейси, це для читабельності - публічний інтерфейс IList: ICollection, IEnumerable
David Klempfner

@Backwards_Dave Я насправді (все ще) думаю, що це робить менш читабельним, оскільки додає зайвого шуму, але я розумію, що ви сказали, і вважаю, що це обгрунтована думка.
rsenna

24

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

Якщо ви дійсно хочете зробити це самостійно, вам також потрібно повернути загальний перелік. Ви більше не зможете користуватися, ArrayListоскільки це не є загальним. Змініть його на List<MyObject>замість цього. Звичайно, передбачається, що MyObjectу вашій колекції є лише об'єкти типу (або похідні типи).


2
+1, слід зазначити, що кращим шаблоном є реалізація загального інтерфейсу та явна реалізація негенерального інтерфейсу. Повернення виходу є найбільш природним рішенням загального інтерфейсу.
user7116

5
Якщо ОП не буде виконувати певну обробку в обчислювачі, використання віддачі прибутків просто додає накладні витрати іншої машини. ОП повинен просто повернути перелік, наданий базовим загальним зібранням.
Монро Томас

@MonroeThomas: Ти маєш рацію. Я не дуже читав питання, перш ніж писати yield return. Я помилково припустив, що є якась спеціальна обробка.
Андерс Абель

4

Якщо ви працюєте з дженериками, використовуйте List замість ArrayList. У списку є саме той метод GetEnumerator, який вам потрібен.

List<MyObject> myList = new List<MyObject>();

0

перетворити мій список у "" - List<MyObject>це один із варіантів


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рекомендується лише в тому випадку, коли потрібна спеціальна поведінка щодо збирання, що трапляється рідко. див. використання .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.