Как в C # получить универсальный перечислитель из заданного массива?
В приведенном ниже коде MyArray
- это массив MyType
объектов. Я хотел бы получить MyIEnumerator
показанным способом, но мне кажется, что я получаю пустой счетчик (хотя я это подтвердил MyArray.Length > 0
).
MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;
c#
arrays
generics
ienumerator
ДжейсонФикс
источник
источник
Ответы:
Работает на 2.0+:
Работает на 3.5+ (причудливый LINQy, немного менее эффективный):
myArray.Cast<MyType>().GetEnumerator() // returns IEnumerator<MyType>
источник
LINQ/Cast
имеешь совершенно другое поведение во время выполнения, так как каждый элемент массива будет передаваться через дополнительный наборMoveNext
иCurrent
нумератор спуско-, и это может повлиять на производительность , если массив огромен. В любом случае проблемы полностью и легко можно избежать, получив в первую очередь правильный счетчик (т. Е. Используя один из двух методов, показанных в моем ответе).myArray.Cast<MyType>().GetEnumerator()
свой самый внутренний цикл, это может значительно замедлить работу даже для крошечных массивов.Вы можете решить для себя, достаточно ли уродливое приведение, чтобы оправдать посторонний вызов библиотеки:
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() }
И для полноты картины , следует также отметить , что следующее не правильно - и будет врезаться во время выполнения - потому что
T[]
выбираешь не -генерическийIEnumerable
интерфейс для своего значения по умолчанию (т.е. без явной) реализации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> }
Загадка заключается в том, почему не
SZGenericArrayEnumerator<T>
наследуется отSZArrayEnumerator
- внутреннего класса, который в настоящее время помечен как «запечатанный» - поскольку это позволило бы (ковариантному) универсальному перечислителю возвращаться по умолчанию?источник
((IEnumerable<int>)arr)
но только один набор скобок(IEnumerator<int>)arr
?Так как я не люблю кастинг, небольшое обновление:
источник
your_array.AsEnumerable()
не будет компилироваться в первую очередь, посколькуAsEnumerable()
может использоваться только в экземплярах типов, которые реализуютIEnumerable
.Чтобы сделать его максимально чистым, я предпочитаю, чтобы всю работу делал компилятор. Нет никаких приведений (так что это действительно типобезопасно). Никакие сторонние библиотеки (System.Linq) не используются (без дополнительных затрат времени выполнения).
public static IEnumerable<T> GetEnumerable<T>(this T[] arr) { return arr; }
// И использовать код:
String[] arr = new String[0]; arr.GetEnumerable().GetEnumerator()
Это использует некоторую магию компилятора, которая сохраняет все в чистоте.
Также следует отметить, что мой ответ - единственный ответ, который будет выполнять проверку во время компиляции.
Для любого из других решений, если тип "arr" изменится, вызывающий код будет компилироваться и завершится ошибкой во время выполнения, что приведет к ошибке времени выполнения.
Мой ответ приведет к тому, что код не будет компилироваться, и, следовательно, у меня меньше шансов отправить ошибку в моем коде, поскольку это будет сигнализировать мне, что я использую неправильный тип.
источник
Foo[]
реализуетIEnumerable<Foo>
, но если это когда-либо изменится, это не будет обнаружено во время компиляции. Явное приведение типов никогда не является доказательством времени компиляции. Вместо этого присвоить / вернуть массив, поскольку IEnumerable <Foo> использует неявное приведение, которое является доказательством времени компиляции.var foo = (int)new object()
. Он отлично компилируется и вылетает во время выполнения.YourArray.OfType (). GetEnumerator ();
может работать немного лучше, поскольку ему нужно только проверять тип, а не приведение.
источник
OfType<..type..>()
- по крайней мере, в моем случаеdouble[][]
MyType[] arr = { new MyType(), new MyType(), new MyType() }; IEnumerable<MyType> enumerable = arr; IEnumerator<MyType> en = enumerable.GetEnumerator(); foreach (MyType item in enumerable) { }
источник
Конечно, вы можете просто реализовать свой собственный универсальный перечислитель для массивов.
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. */} } }
Это более или менее похоже на реализацию .NET SZGenericArrayEnumerator <T>, как упомянул Гленн Слейден. Конечно, вы должны делать это только в тех случаях, когда это того стоит. В большинстве случаев это не так.
источник