Dalam C #, bagaimana seseorang mendapatkan pencacah generik dari larik yang diberikan?
Dalam kode di bawah ini, MyArray
adalah larik MyType
objek. Saya ingin mendapatkan MyIEnumerator
dalam 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>;
c#
arrays
generics
ienumerator
JaysonFix
sumber
sumber
Jawaban:
Bekerja pada 2.0+:
Bekerja pada 3.5+ (LINQy mewah, sedikit kurang efisien):
myArray.Cast<MyType>().GetEnumerator() // returns IEnumerator<MyType>
sumber
LINQ/Cast
memiliki perilaku waktu proses yang sangat berbeda, karena setiap elemen dari array akan diteruskan melalui satu set ekstraMoveNext
danCurrent
perjalanan 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).myArray.Cast<MyType>().GetEnumerator()
loop terdalam Anda, ini mungkin memperlambat Anda secara signifikan bahkan untuk array kecil.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- generikIEnumerable
untuk 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 -SZArrayEnumerator
kelas internal yang saat ini ditandai 'disegel' - karena ini akan memungkinkan pencacah umum (kovarian) dikembalikan secara default?sumber
((IEnumerable<int>)arr)
tetapi hanya menggunakan satu tanda kurung(IEnumerator<int>)arr
?Karena saya tidak suka mentransmisi, sedikit pembaruan:
sumber
your_array.AsEnumerable()
tidak akan dikompilasi sejak awal karenaAsEnumerable()
hanya dapat digunakan pada instance dari tipe yang diimplementasikanIEnumerable
.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.
sumber
Foo[]
implementasinyaIEnumerable<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.var foo = (int)new object()
. Itu mengkompilasi dengan baik dan crash saat runtime.YourArray.OfType (). GetEnumerator ();
mungkin bekerja sedikit lebih baik, karena hanya perlu memeriksa jenisnya, dan tidak melakukan cast.
sumber
OfType<..type..>()
- setidaknya dalam kasus sayadouble[][]
MyType[] arr = { new MyType(), new MyType(), new MyType() }; IEnumerable<MyType> enumerable = arr; IEnumerator<MyType> en = enumerable.GetEnumerator(); foreach (MyType item in enumerable) { }
sumber
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.
sumber