получить универсальный перечислитель из массива


92

Как в C # получить универсальный перечислитель из заданного массива?

В приведенном ниже коде MyArray- это массив MyTypeобъектов. Я хотел бы получить MyIEnumeratorпоказанным способом, но мне кажется, что я получаю пустой счетчик (хотя я это подтвердил MyArray.Length > 0).

MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;

Просто из любопытства, зачем вам счетчик?
Дэвид Клемпфнер 07

1
@Backwards_Dave в моем случае в однопоточной среде есть список файлов, каждый из которых должен быть обработан один раз в асинхронном режиме. Я мог бы использовать индекс для увеличения, но перечисления круче :)
hpaknia

Ответы:


104

Работает на 2.0+:

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

Работает на 3.5+ (причудливый LINQy, немного менее эффективный):

myArray.Cast<MyType>().GetEnumerator()   // returns IEnumerator<MyType>

3
Код LINQy на самом деле возвращает перечислитель для результата метода Cast, а не перечислитель для массива ...
Гуффа,

1
Guffa: поскольку счетчики предоставляют доступ только для чтения, это не большая разница с точки зрения использования.
Мехрдад Афшари,

8
Как следует @Mehrdad, используя LINQ/Castимеешь совершенно другое поведение во время выполнения, так как каждый элемент массива будет передаваться через дополнительный набор MoveNextи Currentнумератор спуско-, и это может повлиять на производительность , если массив огромен. В любом случае проблемы полностью и легко можно избежать, получив в первую очередь правильный счетчик (т. Е. Используя один из двух методов, показанных в моем ответе).
Glenn Slayden

7
Первый вариант лучше! Не позволяйте никому обманываться совершенно излишней "навороченной" версией LINQy.
henon 02

@GlennSlayden Это не только медленно для огромных массивов, но и медленно для массивов любого размера. Поэтому, если вы используете myArray.Cast<MyType>().GetEnumerator()свой самый внутренний цикл, это может значительно замедлить работу даже для крошечных массивов.
Евгений Березовский

54

Вы можете решить для себя, достаточно ли уродливое приведение, чтобы оправдать посторонний вызов библиотеки:

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?
Дэвид Клемпфнер 07

1
@Backwards_Dave Да, именно в этом разница между правильными примерами вверху и неправильными внизу. Проверьте приоритет унарного оператора C # приведения типов .
Гленн

24

Так как я не люблю кастинг, небольшое обновление:

your_array.AsEnumerable().GetEnumerator();

AsEnumerable () также выполняет преобразование, так что вы все еще выполняете приведение :) Возможно, вы могли бы использовать your_array.OfType <T> () .GetEnumerator ();
Mladen B.

1
Разница в том, что это неявное приведение, выполняемое во время компиляции, а не явное приведение во время выполнения. Следовательно, если тип неправильный, у вас будут ошибки времени компиляции, а не ошибки времени выполнения.
Хэнк Шульц

@HankSchultz, если тип неправильный, тогда your_array.AsEnumerable()не будет компилироваться в первую очередь, поскольку AsEnumerable()может использоваться только в экземплярах типов, которые реализуют IEnumerable.
Дэвид Клемпфнер 07

5

Чтобы сделать его максимально чистым, я предпочитаю, чтобы всю работу делал компилятор. Нет никаких приведений (так что это действительно типобезопасно). Никакие сторонние библиотеки (System.Linq) не используются (без дополнительных затрат времени выполнения).

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

// И использовать код:

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

Это использует некоторую магию компилятора, которая сохраняет все в чистоте.

Также следует отметить, что мой ответ - единственный ответ, который будет выполнять проверку во время компиляции.

Для любого из других решений, если тип "arr" изменится, вызывающий код будет компилироваться и завершится ошибкой во время выполнения, что приведет к ошибке времени выполнения.

Мой ответ приведет к тому, что код не будет компилироваться, и, следовательно, у меня меньше шансов отправить ошибку в моем коде, поскольку это будет сигнализировать мне, что я использую неправильный тип.


Приведение, показанное GlennSlayden и MehrdadAfshari, также является доказательством времени компиляции.
t3chb0t

1
@ t3chb0t нет, это не так. Работа во время выполнения, потому что мы знаем, что Foo[]реализует IEnumerable<Foo>, но если это когда-либо изменится, это не будет обнаружено во время компиляции. Явное приведение типов никогда не является доказательством времени компиляции. Вместо этого присвоить / вернуть массив, поскольку IEnumerable <Foo> использует неявное приведение, которое является доказательством времени компиляции.
Toxantron

@Toxantron Явное приведение к IEnumerable <T> не будет компилироваться, если тип, который вы пытаетесь преобразовать, не реализует IEnumerable. Когда вы говорите «но если это когда-нибудь изменится», можете ли вы привести пример того, что вы имеете в виду?
Дэвид Клемпфнер 07

Конечно компилируется. Вот для чего нужен InvalidCastException . Ваш компилятор может предупредить вас, что определенное приведение не работает. Попробуй var foo = (int)new object(). Он отлично компилируется и вылетает во время выполнения.
Toxantron

2

YourArray.OfType (). GetEnumerator ();

может работать немного лучше, поскольку ему нужно только проверять тип, а не приведение.


Вы должны явно указать тип при использовании OfType<..type..>()- по крайней мере, в моем случаеdouble[][]
М. Мимпен

0
    MyType[] arr = { new MyType(), new MyType(), new MyType() };

    IEnumerable<MyType> enumerable = arr;

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

    foreach (MyType item in enumerable)
    {

    }

Не могли бы вы объяснить, что будет выполнять приведенный выше код>
Phani

Это должно быть голосование намного выше! Использование неявного приведения компилятора - самое чистое и простое решение.
Toxantron

-1

Конечно, вы можете просто реализовать свой собственный универсальный перечислитель для массивов.

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>, как упомянул Гленн Слейден. Конечно, вы должны делать это только в тех случаях, когда это того стоит. В большинстве случаев это не так.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.