obtain generic enumerator from an array
Asked Answered
Q

8

113

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>;
Quotient answered 13/8, 2009 at 15:21 Comment(2)
Just out of curiosity, why do you want to get the enumerator?Sturtevant
@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 :)Bekah
P
120

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>
Polypary answered 13/8, 2009 at 15:21 Comment(6)
The LINQy code actually returns an enumerator for the result of the Cast method, rather than an enumerator for the array...Milkman
Guffa: since enumerators provide read-only access only, it's not a big difference in terms of usage.Polypary
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).Lampoon
The first is the better option! Don't anybody let oneself deceive by the "fancy LINQy" version which is completely superfluous.Refectory
@GlennSlayden It's not only slow for huge arrays, it's slow for any size of arrays. So if you use myArray.Cast<MyType>().GetEnumerator() in your innermost loop, it may slow you down significantly even for tiny arrays.Tini
@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.Lampoon
L
67

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?

Lampoon answered 31/7, 2012 at 0:14 Comment(4)
Is there a reason why you used extra brackets in ((IEnumerable<int>)arr) but only one set of brackets in (IEnumerator<int>)arr?Sturtevant
@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 of the C# unary "cast" operator.Lampoon
@GlennSlayden Would the IL for as be more optimal than casting?Illaffected
"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.Lampoon
B
30

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

your_array.AsEnumerable().GetEnumerator();
Bigford answered 6/4, 2012 at 16:10 Comment(3)
AsEnumerable() also does the casting, so you're still doing the cast :) Maybe you could use your_array.OfType<T>().GetEnumerator();Bali
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.Vermillion
@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.Sturtevant
V
9

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.

Varnado answered 30/5, 2014 at 2:45 Comment(4)
Casting as shown by GlennSlayden and MehrdadAfshari is also compile-time proof.Micky
@Micky no they are not. The work at runtime because we know that Foo[] implements IEnumerable<Foo>, 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<Foo> uses the implicit cast which is compile-time proof.Heteropterous
@Heteropterous An explicit cast to IEnumerable<T> 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?Sturtevant
Of course it compiles. That's what InvalidCastException 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.Heteropterous
B
2

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

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

Burrows answered 4/4, 2014 at 20:59 Comment(2)
You have to explicitly specify the type when using OfType<..type..>() - at least in my case of double[][]Chee
@M.Mimpen good catch. the mark up language ate my unescaped \< and \>Burrows
S
2
    MyType[] arr = { new MyType(), new MyType(), new MyType() };

    IEnumerable<MyType> enumerable = arr;

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

    foreach (MyType item in enumerable)
    {

    }
Syphilology answered 11/1, 2016 at 14:48 Comment(2)
Can you please explain what above code would perform>Douglass
This should be vote much higher! Using the implicit cast of the compiler is the cleanest and easiest solution.Heteropterous
H
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
}
Howells answered 16/4, 2023 at 18:32 Comment(0)
M
-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.

Mint answered 16/7, 2016 at 11:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.