mendapatkan pencacah generik dari array

92

Dalam C #, bagaimana seseorang mendapatkan pencacah generik dari larik yang diberikan?

Dalam kode di bawah ini, MyArrayadalah larik MyTypeobjek. Saya ingin mendapatkan MyIEnumeratordalam mode yang ditunjukkan, tetapi tampaknya saya mendapatkan pencacah kosong (meskipun saya sudah memastikannya MyArray.Length > 0).

MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;
JaysonFix
sumber
Hanya karena penasaran, mengapa Anda ingin mendapatkan pencacah?
David Klempfner
1
@Backwards_Dave dalam kasus saya dalam satu lingkungan threaded ada daftar file yang masing-masing harus diproses sekali, dengan cara Async. Saya dapat menggunakan indeks untuk meningkatkan, tetapi enumerabel lebih keren :)
hpaknia

Jawaban:

104

Bekerja pada 2.0+:

((IEnumerable<MyType>)myArray).GetEnumerator()

Bekerja pada 3.5+ (LINQy mewah, sedikit kurang efisien):

myArray.Cast<MyType>().GetEnumerator()   // returns IEnumerator<MyType>
Mehrdad Afshari
sumber
3
Kode LINQy sebenarnya mengembalikan enumerator untuk hasil metode Cast, daripada enumerator untuk array ...
Guffa
1
Guffa: karena enumerator hanya menyediakan akses read-only, tidak ada perbedaan besar dalam hal penggunaan.
Mehrdad Afshari
8
Seperti yang diimplikasikan oleh @Mehrdad, Using LINQ/Castmemiliki perilaku waktu proses yang sangat berbeda, karena setiap elemen dari array akan diteruskan melalui satu set ekstra MoveNextdan Currentperjalanan bolak-balik enumerator, dan ini dapat memengaruhi kinerja jika array sangat besar. Bagaimanapun, masalah sepenuhnya dan mudah dihindari dengan mendapatkan pencacah yang tepat di tempat pertama (yaitu, menggunakan salah satu dari dua metode yang ditunjukkan dalam jawaban saya).
Glenn Slayden
7
Yang pertama adalah opsi yang lebih baik! Jangan ada yang membiarkan dirinya tertipu oleh versi "LINQy mewah" yang sama sekali tidak berguna.
henon
@GlennSlayden Ini tidak hanya lambat untuk array besar, tapi juga lambat untuk semua ukuran array. Jadi, jika Anda menggunakan myArray.Cast<MyType>().GetEnumerator()loop terdalam Anda, ini mungkin memperlambat Anda secara signifikan bahkan untuk array kecil.
Evgeniy Berezovsky
54

Anda dapat memutuskan sendiri apakah casting cukup jelek untuk menjamin panggilan perpustakaan yang tidak relevan:

int[] arr;
IEnumerator<int> Get1()
{
    return ((IEnumerable<int>)arr).GetEnumerator();  // <-- 1 non-local call

    // ldarg.0 
    // ldfld int32[] foo::arr
    // castclass System.Collections.Generic.IEnumerable`1<int32>
    // callvirt instance class System.Collections.Generic.IEnumerator`1<!0> System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
}

IEnumerator<int> Get2()
{
    return arr.AsEnumerable().GetEnumerator();   // <-- 2 non-local calls

    // ldarg.0 
    // ldfld int32[] foo::arr
    // call class System.Collections.Generic.IEnumerable`1<!!0> System.Linq.Enumerable::AsEnumerable<int32>(class System.Collections.Generic.IEnumerable`1<!!0>)
    // callvirt instance class System.Collections.Generic.IEnumerator`1<!0> System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
}

Dan untuk kelengkapan, kita juga harus mencatat bahwa berikut ini tidak benar - dan akan macet saat runtime - karena T[]memilih antarmuka non- generik IEnumerableuntuk implementasi defaultnya (yaitu non-eksplisit) GetEnumerator().

IEnumerator<int> NoGet()                    // error - do not use
{
    return (IEnumerator<int>)arr.GetEnumerator();

    // ldarg.0 
    // ldfld int32[] foo::arr
    // callvirt instance class System.Collections.IEnumerator System.Array::GetEnumerator()
    // castclass System.Collections.Generic.IEnumerator`1<int32>
}

Misterinya adalah, mengapa tidak SZGenericArrayEnumerator<T>mewarisi dari - SZArrayEnumeratorkelas internal yang saat ini ditandai 'disegel' - karena ini akan memungkinkan pencacah umum (kovarian) dikembalikan secara default?

Glenn Slayden
sumber
Apakah ada alasan mengapa Anda menggunakan tanda kurung tambahan ((IEnumerable<int>)arr)tetapi hanya menggunakan satu tanda kurung (IEnumerator<int>)arr?
David Klempfner
1
@Backwards_Dave Ya, itulah yang membuat perbedaan antara contoh yang benar di atas, dan yang salah di bawah. Periksa operator didahulukan dari C # unary operator "cor".
Glenn Slayden
24

Karena saya tidak suka mentransmisi, sedikit pembaruan:

your_array.AsEnumerable().GetEnumerator();
greenoldman
sumber
AsEnumerable () juga melakukan casting, jadi Anda masih melakukan cast :) Mungkin Anda bisa menggunakan your_array.OfType <T> () .GetEnumerator ();
Mladen B.
1
Perbedaannya adalah bahwa ini adalah transmisi implisit yang dilakukan pada waktu kompilasi, bukan transmisi eksplisit yang dilakukan pada waktu proses. Oleh karena itu, Anda akan mengalami error waktu kompilasi daripada error runtime jika jenisnya salah.
Hank Schultz
@HankSchultz jika tipenya salah maka your_array.AsEnumerable()tidak akan dikompilasi sejak awal karena AsEnumerable()hanya dapat digunakan pada instance dari tipe yang diimplementasikan IEnumerable.
David Klempfner
5

Untuk membuatnya sebersih mungkin saya ingin membiarkan compiler melakukan semua pekerjaan. Tidak ada gips (jadi tipe ini aman). Tidak ada Library pihak ketiga (System.Linq) yang digunakan (Tanpa overhead runtime).

    public static IEnumerable<T> GetEnumerable<T>(this T[] arr)
    {
        return arr;
    }

// Dan untuk menggunakan kode:

    String[] arr = new String[0];
    arr.GetEnumerable().GetEnumerator()

Ini memanfaatkan beberapa keajaiban kompiler yang menjaga semuanya tetap bersih.

Hal lain yang perlu diperhatikan adalah bahwa jawaban saya adalah satu-satunya jawaban yang akan melakukan pengecekan waktu kompilasi.

Untuk solusi lain yang mana pun jika jenis "arr" berubah, maka kode pemanggil akan dikompilasi, dan gagal saat runtime, yang mengakibatkan bug runtime.

Jawaban saya akan menyebabkan kode tidak dapat dikompilasi dan oleh karena itu peluang saya untuk mengirimkan bug dalam kode saya lebih kecil, karena ini akan memberi sinyal kepada saya bahwa saya menggunakan jenis yang salah.

leat
sumber
Casting seperti yang ditunjukkan oleh GlennSlayden dan MehrdadAfshari juga merupakan bukti waktu kompilasi.
t3chb0t
1
@ t3chb0t tidak, mereka bukan. Pekerjaan saat runtime karena kita tahu bahwa Foo[]implementasinya IEnumerable<Foo>, tetapi jika itu berubah, itu tidak akan terdeteksi pada waktu kompilasi. Pemeran eksplisit tidak pernah menjadi bukti waktu kompilasi. Alih-alih menetapkan / mengembalikan array sebagai IEnumerable <Foo> menggunakan pemeran implisit yang merupakan bukti waktu kompilasi.
Toxantron
@Toxantron Transmisi eksplisit ke IEnumerable <T> tidak akan dikompilasi kecuali tipe yang Anda coba gunakan mengimplementasikan IEnumerable. Saat Anda mengatakan "tetapi jika itu pernah berubah", dapatkah Anda memberikan contoh tentang apa yang Anda maksud?
David Klempfner
Tentu saja bisa dikompilasi. Untuk itulah InvalidCastException . Kompilator Anda mungkin memperingatkan Anda, bahwa pemeran tertentu tidak berfungsi. Coba var foo = (int)new object(). Itu mengkompilasi dengan baik dan crash saat runtime.
Toxantron
2

YourArray.OfType (). GetEnumerator ();

mungkin bekerja sedikit lebih baik, karena hanya perlu memeriksa jenisnya, dan tidak melakukan cast.

Andrew Dennison
sumber
Anda harus secara eksplisit menentukan tipe saat menggunakan OfType<..type..>()- setidaknya dalam kasus sayadouble[][]
M. Mimpen
0
    MyType[] arr = { new MyType(), new MyType(), new MyType() };

    IEnumerable<MyType> enumerable = arr;

    IEnumerator<MyType> en = enumerable.GetEnumerator();

    foreach (MyType item in enumerable)
    {

    }
Maxim Q
sumber
Bisakah Anda jelaskan apa yang akan dilakukan kode di atas>
Phani
Ini harusnya suara yang jauh lebih tinggi! Menggunakan cast implisit dari compiler adalah solusi terbersih dan termudah.
Toxantron
-1

Yang dapat Anda lakukan, tentu saja, hanyalah mengimplementasikan enumerator generik Anda sendiri untuk array.

using System.Collections;
using System.Collections.Generic;

namespace SomeNamespace
{
    public class ArrayEnumerator<T> : IEnumerator<T>
    {
        public ArrayEnumerator(T[] arr)
        {
            collection = arr;
            length = arr.Length;
        }
        private readonly T[] collection;
        private int index = -1;
        private readonly int length;

        public T Current { get { return collection[index]; } }

        object IEnumerator.Current { get { return Current; } }

        public bool MoveNext() { index++; return index < length; }

        public void Reset() { index = -1; }

        public void Dispose() {/* Nothing to dispose. */}
    }
}

Ini kurang lebih sama dengan implementasi .NET dari SZGenericArrayEnumerator <T> seperti yang disebutkan oleh Glenn Slayden. Anda tentu saja harus melakukan ini, jika ini sepadan dengan usaha. Dalam banyak kasus tidak.

Corniel Nobel
sumber