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


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.