109

In C#, how does one obtain a generic enumerator from a given array?

In the code below, MyArray is an array of MyType objects. I'd like to obtain MyIEnumerator in the fashion shown, but it seems that I obtain an empty enumerator (although I've confirmed that MyArray.Length > 0).

MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;
martijnn2008
  • 3,552
  • 5
  • 30
  • 40
JaysonFix
  • 2,515
  • 9
  • 28
  • 28
  • Just out of curiosity, why do you want to get the enumerator? – David Klempfner Oct 07 '19 at 06:04
  • 1
    @Backwards_Dave in my case in a single threaded environment there is a list of files which each of them must be processed once, in an Async manner. I could use an index to increase, but enumerables are cooler :) – hpaknia Dec 07 '19 at 05:54

8 Answers8

116

Works on 2.0+:

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

Works on 3.5+ (fancy LINQy, a bit less efficient):

myArray.Cast<MyType>().GetEnumerator()   // returns IEnumerator<MyType>
Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
  • 4
    The LINQy code actually returns an enumerator for the result of the Cast method, rather than an enumerator for the array... – Guffa Aug 13 '09 at 15:31
  • 1
    Guffa: since enumerators provide read-only access only, it's not a big difference in terms of usage. – Mehrdad Afshari Aug 13 '09 at 15:34
  • 11
    As @Mehrdad implies, Using `LINQ/Cast` has quite different runtime behavior, since **each and every element of the array** will be passed through an extra set of `MoveNext` and `Current` enumerator round-trips, and this could affect performance if the array is huge. In any case, the problem is completely and easily avoided by obtaining the proper enumerator in the first place (i.e., using one of the two methods shown in my answer). – Glenn Slayden Mar 19 '16 at 21:54
  • 8
    The first is the better option! Don't anybody let oneself deceive by the "fancy LINQy" version which is completely superfluous. – henon Jun 02 '16 at 12:59
  • @GlennSlayden It's not only slow for huge arrays, it's slow for any size of arrays. So if you use `myArray.Cast().GetEnumerator()` in your innermost loop, it may slow you down significantly even for tiny arrays. – Evgeniy Berezovsky Nov 13 '18 at 23:54
  • @EugeneBeresovsky I was trying to be polite.. :-) But seriously, perhaps an even more important issue is whether the *element* type of the underlying array is a reference (`class`) versus value-type (`struct`). Extra by-value shuffling of ***value-types***, when each is larger than a dozen bytes or so, can quickly kill the performance of a **LINQ** pipeline. – Glenn Slayden Nov 14 '18 at 00:46
65

You can decide for yourself whether casting is ugly enough to warrant an extraneous library call:

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()
}

And for completeness, one should also note that the following is not correct--and will crash at runtime--because T[] chooses the non-generic IEnumerable interface for its default (i.e. non-explicit) implementation of 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>
}

The mystery is, why doesn't SZGenericArrayEnumerator<T> inherit from SZArrayEnumerator--an internal class which is currently marked 'sealed'--since this would allow the (covariant) generic enumerator to be returned by default?

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
  • Is there a reason why you used extra brackets in `((IEnumerable)arr)` but only one set of brackets in `(IEnumerator)arr`? – David Klempfner Oct 07 '19 at 06:22
  • 1
    @Backwards_Dave Yes, it's what makes the difference between the correct examples at the top, and the incorrect one at the bottom. Check the [operator precedence](https://learn.microsoft.com/en-us/cpp/c-language/precedence-and-order-of-evaluation?view=vs-2019) of the **C#** unary "cast" operator. – Glenn Slayden Oct 07 '19 at 13:01
  • @GlennSlayden Would the IL for `as` be more optimal than casting? – Rabadash8820 Dec 27 '21 at 08:09
  • "Casting" versus `as` will each directly map to a specific IL instruction of their own -- `castclass` compared to `isinst`. Although "as" intuitively seems to be doing more existential "work" by its definition, the actual performance difference between these two probably is not--under any circumstance--going to be measurable. – Glenn Slayden Dec 27 '21 at 08:21
29

Since I don't like casting, a little update:

your_array.AsEnumerable().GetEnumerator();
greenoldman
  • 16,895
  • 26
  • 119
  • 185
  • AsEnumerable() also does the casting, so you're still doing the cast :) Maybe you could use your_array.OfType().GetEnumerator(); – Mladen B. Mar 07 '18 at 14:54
  • 3
    The difference is that it's an implicit cast done at compile time rather than an explicit cast done at runtime. Therefore, you'll have compile time errors rather than runtime errors if the type is wrong. – Hank Schultz Sep 06 '18 at 20:27
  • @HankSchultz if the type is wrong then `your_array.AsEnumerable()` wouldn't compile in the first place since `AsEnumerable()` can only be used on instances of types that implement `IEnumerable`. – David Klempfner Oct 07 '19 at 06:33
8

To Make it as clean as possible I like to let the compiler do all of the work. There are no casts (so its actually type-safe). No third party Libraries (System.Linq) are used (No runtime overhead).

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

// And to use the code:

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

This takes advantage of some compiler magic that keeps everything clean.

The other point to note is that my answer is the only answer that will do compile-time checking.

For any of the other solutions if the type of "arr" changes, then calling code will compile, and fail at runtime, resulting in a runtime bug.

My answer will cause the code to not compile and therefore I have less chance of shipping a bug in my code, as it would signal to me that I am using the wrong type.

leat
  • 1,418
  • 1
  • 15
  • 21
  • 1
    Casting as shown by GlennSlayden and MehrdadAfshari is also compile-time proof. – t3chb0t Sep 28 '15 at 07:39
  • 2
    @t3chb0t no they are not. The work at runtime **because** we know that `Foo[]` implements `IEnumerable`, but if that ever changes it will not be detected at compile time. Explicit casts are never compile-time proof. Instead assign/returning array as IEnumerable uses the implicit cast which is compile-time proof. – Toxantron Mar 15 '18 at 09:41
  • @Toxantron An explicit cast to IEnumerable wouldn't compile unless the type you're trying to cast implements IEnumerable. When you say "but if that ever changes", can you provide an example of what you mean? – David Klempfner Oct 07 '19 at 06:54
  • 1
    Of course it compiles. That's what [InvalidCastException](https://learn.microsoft.com/dotnet/api/system.invalidcastexception?view=netframework-4.8) is for. Your compiler might warn you, that a certain cast does not work. Try `var foo = (int)new object()`. It compiles just fine and crashes at runtime. – Toxantron Oct 14 '19 at 08:42
2

YourArray.OfType<StringId>().GetEnumerator();

may perform a little better, since it only has to check the type, and not cast.

Andrew Dennison
  • 1,069
  • 11
  • 9
1
    MyType[] arr = { new MyType(), new MyType(), new MyType() };

    IEnumerable<MyType> enumerable = arr;

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

    foreach (MyType item in enumerable)
    {

    }
Maxim Q
  • 19
  • 1
  • 2
0

Since @Mehrdad Afshari answer i made extension method for that:

public static IEnumerator<T> GetEnumerator<T>(this T[] array) {
    return (IEnumerator<T>)array.GetEnumerator();
}

So you can implement IEnumerable like this:

public class MulticoloredLine : IEnumerable<ColoredLine> {
    private ColoredLine[] coloredLines;    

    #region IEnumerable<ColoredLine>
    public IEnumerator<ColoredLine> GetEnumerator() => 
    coloredLines.GetEnumerator<ColoredLine>();
    IEnumerator IEnumerable.GetEnumerator() => coloredLines.GetEnumerator();
    #endregion
}
VladisS
  • 11
  • 5
-1

What you can do, of course, is just implement your own generic enumerator for arrays.

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. */}
    }
}

This is more or less equal to the .NET implemenation of SZGenericArrayEnumerator<T> as mentioned by Glenn Slayden. You should of course only do this, is cases where this is worth the effort. In most cases it is not.

Corniel Nobel
  • 421
  • 4
  • 12