Bagaimana cara mengimplementasikan IEnumerable <T>

124

Saya tahu bagaimana menerapkan IEnumerable non generik, seperti ini:

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

Namun saya juga memperhatikan bahwa IEnumerable memiliki versi generik IEnumerable<T>, tetapi saya tidak tahu cara menerapkannya.

Jika saya menambahkan using System.Collections.Generic;ke petunjuk penggunaan saya, dan kemudian mengubah:

class MyObjects : IEnumerable

untuk:

class MyObjects : IEnumerable<MyObject>

Dan kemudian klik kanan IEnumerable<MyObject>dan pilih Implement Interface => Implement Interface, Visual Studio dengan membantu menambahkan blok kode berikut:

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

Mengembalikan objek IEnumerable non generik dari GetEnumerator();metode tidak berfungsi kali ini, jadi apa yang harus saya taruh di sini? CLI sekarang mengabaikan implementasi non generik dan langsung menuju ke versi generik ketika mencoba untuk menghitung melalui array saya selama loop foreach.

JMK
sumber

Jawaban:

149

Jika Anda memilih untuk menggunakan koleksi generik, seperti List<MyObject>daripada ArrayList, Anda akan menemukan bahwa List<MyObject>akan menyediakan enumerator generik dan non-generik yang dapat Anda gunakan.

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();
    }
}
Monroe Thomas
sumber
1
Dalam implementasi non-generik, apakah ada perbedaan antara kembali this.GetEnumerator()dan hanya kembali GetEnumerator()?
Tanner Swett
1
@TannerSwett Tidak ada perbedaan.
Monroe Thomas
Anda mungkin juga mewarisi dari Collection<MyObject>dan kemudian Anda tidak perlu menulis kode khusus apa pun.
John Alexiou
@ ja72 Bagaimana jika Anda sudah mewarisi dari kelas dasar lain dan tidak dapat mewarisi dari Collection <MyObject>?
Monroe Thomas
1
@MonroeThomas - maka Anda harus membungkus List<T>dan menerapkan IEnumerable<T>seperti yang ditunjukkan dalam jawaban Anda.
John Alexiou
69

Anda mungkin tidak menginginkan implementasi eksplisitIEnumerable<T> (seperti yang telah Anda tunjukkan).

Pola yang biasa adalah dengan menggunakan IEnumerable<T>'s GetEnumeratordalam pelaksanaan eksplisit 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();
    }
}
pengguna7116
sumber
2
Ini adalah solusi yang baik jika SomeCollection <T> belum mengimplementasikan IEnumerable <T>, atau jika Anda perlu mengubah atau melakukan pemrosesan lain pada setiap elemen sebelum dikembalikan dalam urutan pencacahan. Jika tidak satu pun dari kondisi ini benar, maka mungkin lebih efisien untuk hanya mengembalikan this.foos.GetEnumerator ();
Monroe Thomas
@MonroeThomas: setuju. Tidak tahu koleksi apa yang digunakan OP.
pengguna7116
8
Poin yang bagus. Tetapi penerapan IEnumerableitu mubazir ( IEnumerable<T>sudah diwarisi darinya).
rsenna
@rsenna itu benar, namun perlu diingat bahkan antarmuka .NET seperti IList mengimplementasikan antarmuka secara berlebihan, itu untuk keterbacaan - antarmuka publik IList: ICollection, IEnumerable
David Klempfner
@Backwards_Dave Saya sebenarnya (masih) berpikir itu membuat kurang terbaca, karena menambahkan suara yang tidak perlu, tapi saya mengerti apa yang Anda katakan, dan menganggap itu pendapat yang valid.
rsenna
24

Mengapa Anda melakukannya secara manual? yield returnmengotomatiskan seluruh proses penanganan iterator. (Saya juga menulis tentang itu di blog saya , termasuk melihat kode yang dihasilkan kompiler).

Jika Anda benar-benar ingin melakukannya sendiri, Anda harus mengembalikan enumerator generik juga. Anda tidak akan dapat menggunakan ArrayListlagi karena itu non-generik. Ubah ke a List<MyObject>sebagai gantinya. Itu tentu saja mengasumsikan bahwa Anda hanya memiliki objek bertipe MyObject(atau tipe turunan) dalam koleksi Anda.

Anders Abel
sumber
2
+1, perlu diperhatikan bahwa pola yang disukai adalah mengimplementasikan antarmuka generik dan secara eksplisit mengimplementasikan antarmuka non-generik. Pengembalian hasil adalah solusi paling alami untuk antarmuka generik.
pengguna7116
5
Kecuali jika OP akan melakukan beberapa pemrosesan di enumerator, menggunakan pengembalian hasil hanya menambahkan overhead mesin negara bagian lain. OP seharusnya mengembalikan enumerator yang disediakan oleh koleksi generik yang mendasarinya.
Monroe Thomas
@MonroeThomas: Anda benar. Saya tidak membaca pertanyaan dengan cermat sebelum menulis tentang yield return. Saya salah berasumsi bahwa ada beberapa pemrosesan khusus.
Anders Abel
4

Jika Anda bekerja dengan obat generik, gunakan List, bukan ArrayList. List tersebut memiliki metode GetEnumerator yang Anda butuhkan.

List<MyObject> myList = new List<MyObject>();
Amiram Korach
sumber
0

membuat mylist menjadi List<MyObject>, adalah salah satu pilihan

ColWhi
sumber
0

Perhatikan bahwa semua IEnumerable<T>sudah diimplementasikan oleh System.Collectionsjadi pendekatan lain adalah untuk menurunkan MyObjectskelas Anda dari System.Collectionssebagai kelas dasar ( dokumentasi ):

System.Collections: Menyediakan kelas dasar untuk koleksi generik.

Kami kemudian dapat membuat implemenation kita sendiri untuk menimpa virtual System.Collectionsmetode untuk memberikan perilaku kustom (hanya untuk ClearItems, InsertItem, RemoveItem, dan SetItembersama dengan Equals, GetHashCode, dan ToStringdari Object). Berbeda dengan List<T>yang tidak didesain agar mudah dikembangkan.

Contoh:

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!
}

Harap dicatat bahwa mengemudi dari yang collectiondisarankan hanya jika diperlukan perilaku pengumpulan khusus, yang jarang terjadi. lihat penggunaan .

Shahar Shokrani
sumber