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


91

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

У наведеному нижче коді MyArrayє масив MyTypeоб’єктів. Я хотів би отримати MyIEnumeratorза показаним способом, але, схоже, я отримую порожній перелічувач (хоча я це підтвердив MyArray.Length > 0).

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

Просто з цікавості, чому ви хочете отримати переписчика?
— Девід Клемпфнер

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

Відповіді:


102

Працює на 2.0+:

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

Працює на версії 3.5+ (химерний LINQy, трохи менш ефективний):

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

3
Код LINQy насправді повертає перечислювач результату методу Cast, а не перечислювач масиву ...
— Guffa

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

8
Як мається на увазі @Mehrdad, Use LINQ/Castмає досить різну поведінку під час виконання, оскільки кожен елемент масиву буде переданий через додатковий набір MoveNextта Currentперерахування туди і назад, і це може вплинути на продуктивність, якщо масив величезний. У будь-якому випадку проблему можна повністю і легко уникнути, отримавши в першу чергу відповідний перерахувальник (тобто, використовуючи один із двох методів, показаних у моїй відповіді).
— Гленн Слейден,

7
Перший - кращий варіант! Ніхто не дозволяйте себе обманювати "химерною версією LINQy", яка є абсолютно зайвою.
— henon

@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?
— Девід Клемпфнер

1
@Backwards_Dave Так, саме це робить різницю між правильними прикладами вгорі та неправильними внизу. Перевірте пріоритет операторів в C # унарних «литий».
— Гленн Слейден,

24

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

your_array.AsEnumerable().GetEnumerator();

AsEnumerable () також виконує кастинг, тому ви все ще робите акторський склад :) Можливо, ви могли б використати your_array.OfType <T> () .GetEnumerator ();
— Mladen B.

1
Різниця полягає в тому, що це неявний привід, виконаний під час компіляції, а не явний привід, виконаний під час виконання. Таким чином, ви матимете помилки часу компіляції, а не помилки виконання, якщо тип помилковий.
— Hank Schultz

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

5

Щоб зробити це максимально чистим, я люблю дозволяти компілятору виконувати всю роботу. Немає зліпків (тому насправді безпечно для типу). Не використовуються сторонні бібліотеки (System.Linq) (відсутність накладних витрат на виконання).

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

// А для використання коду:

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

Це використовує певну магію компілятора, яка підтримує все в чистоті.

Інший момент, на який слід звернути увагу, полягає в тому, що моя відповідь - це єдина відповідь, яка буде перевіряти час компіляції.

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

Моя відповідь призведе до того, що код не буде скомпільований, і тому у мене менше шансів надіслати помилку в моєму коді, оскільки це буде сигналом для мене, що я використовую неправильний тип.


Кастинг, як показали ГленнСлайден та МехрдадАфшарі, також є підтвердженням часу компіляції.
— t3chb0t

1
@ t3chb0t ні вони не є. Робота під час виконання, тому що ми знаємо, що Foo[]реалізується IEnumerable<Foo>, але якщо це коли-небудь зміниться, вона не буде виявлена ​​під час компіляції. Явні складання ніколи не є доказом часу компіляції. Натомість призначити / повернути масив, оскільки IEnumerable <Foo> використовує неявний привід, який є доказом часу компіляції.
— Токсантрон

@Toxantron Явний привід до IEnumerable <T> не компілюється, якщо тип, який ви намагаєтесь привести, не реалізує IEnumerable. Коли ви говорите "але якщо це колись зміниться", чи можете ви навести приклад того, що ви маєте на увазі?
— Девід Клемпфнер

Звичайно, це компілюється. Саме для цього призначений InvalidCastException . Ваш компілятор може попередити вас, що певний склад не працює. Спробуйте var foo = (int)new object(). Він чудово компілюється і аварійно завершує роботу під час роботи.
— Токсантрон

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)
    {

    }

Ви можете пояснити, що буде виконувати наведений вище код>
— Фані

Це має бути голосом набагато вище! Використання неявного складання компілятора є найчистішим і найпростішим рішенням.
— Токсантрон

-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.