Получить универсальный перечислитель из массива
В C# как получить общий перечислитель из данного массива?
В приведенном ниже коде MyArray
это массив MyType
объекты. Я хотел бы получить MyIEnumerator
показанным способом, но кажется, что я получаю пустой перечислитель (хотя я подтвердил, что MyArray.Length > 0
).
MyType [ ] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator
= ( MyArray.GetEnumerator() as IEnumerator<MyType> ) ;
8 ответов
Работает на 2.0+:
((IEnumerable<MyType>)myArray).GetEnumerator()
Работает на 3.5+ (причудливый LINQy, чуть менее эффективный):
myArray.Cast<MyType>().GetEnumerator() // returns IEnumerator<MyType>
Вы можете решить для себя, достаточно ли уродливость, чтобы оправдать посторонний вызов библиотеки:
int[] arr;
IEnumerator<int> Get1()
{
return ((IEnumerable<int>)arr).GetEnumerator(); // <-- 1 non-local call
// L_0001: ldarg.0
// L_0002: ldfld int32[] foo::arr
// L_0007: castclass [mscorlib]System.Collections.Generic.IEnumerable`1<int32>
// L_000c: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
// L_0011: stloc.0
}
IEnumerator<int> Get2()
{
return arr.AsEnumerable().GetEnumerator(); // <-- 2 non-local calls
// L_0001: ldarg.0
// L_0002: ldfld int32[] foo::arr
// L_0007: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::AsEnumerable<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
// L_000c: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
// L_0011: stloc.0
}
И для полноты следует также отметить, что следующее не является правильным - и будет сбой во время выполнения - потому что T[]
выбирает не родовой IEnumerable
интерфейс для его реализации по умолчанию (то есть не явной) GetEnumerator()
,
IEnumerator<int> NoGet() // error - do not use
{
return (IEnumerator<int>)arr.GetEnumerator();
// L_0001: ldarg.0
// L_0002: ldfld int32[] foo::arr
// L_0007: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator()
// L_000c: castclass [mscorlib]System.Collections.Generic.IEnumerator`1<int32>
// L_0011: stloc.0
}
Тайна в том, почему не SZGenericArrayEnumerator<T>
наследовать от SZArrayEnumerator
- внутренний класс, который в настоящее время помечен как "запечатанный" - поскольку это позволило бы (по-ковариантному) универсальному перечислителю быть возвращенным по умолчанию?
Так как я не люблю кастинг, небольшое обновление:
your_array.AsEnumerable().GetEnumerator();
Чтобы сделать его максимально чистым, мне хотелось бы, чтобы компилятор делал всю работу. Там нет приведений (так что на самом деле тип-безопасно). Никакие сторонние библиотеки (System.Linq) не используются (без затрат времени выполнения).
public static IEnumerable<T> GetEnumerable<T>(this T[] arr)
{
return arr;
}
// И использовать код:
String[] arr = new String[0];
arr.GetEnumerable().GetEnumerator()
Это использует преимущества магии компилятора, которая поддерживает все в чистоте.
Другой момент, на который следует обратить внимание: мой ответ - единственный ответ, который будет выполнять проверку во время компиляции.
Для любого другого решения, если тип "arr" изменится, вызывающий код скомпилируется и завершится с ошибкой во время выполнения, что приведет к ошибке во время выполнения.
Мой ответ приведет к тому, что код не скомпилируется, и поэтому у меня меньше шансов отправить ошибку в моем коде, так как это будет сигнализировать мне, что я использую неправильный тип.
. YourArray.OfType() GetEnumerator();
может работать немного лучше, так как нужно только проверять тип, а не приводить.
MyType[] arr = { new MyType(), new MyType(), new MyType() };
IEnumerable<MyType> enumerable = arr;
IEnumerator<MyType> en = enumerable.GetEnumerator();
foreach (MyType item in enumerable)
{
}
После ответа @Mehrdad Afshari я создал для этого метод расширения:
public static IEnumerator<T> GetEnumerator<T>(this T[] array) {
return (IEnumerator<T>)array.GetEnumerator();
}
Итак, вы можете реализовать IEnumerable следующим образом:
public class MulticoloredLine : IEnumerable<ColoredLine> {
private ColoredLine[] coloredLines;
#region IEnumerable<ColoredLine>
public IEnumerator<ColoredLine> GetEnumerator() =>
coloredLines.GetEnumerator<ColoredLine>();
IEnumerator IEnumerable.GetEnumerator() => coloredLines.GetEnumerator();
#endregion
}
Конечно, вы можете просто реализовать свой собственный перечислитель для массивов.
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