87

I have just noticed that a multidimensional array in C# does not implement IEnumerable<T>, while it does implement IEnumerable. For single-dimensional arrays, both IEnumerable<T> and IEnumerable are implemented.

Why this difference? If a multi-dimensional array is IEnumerable, surely it should also implement the generic version? I noticed this because I tried to use an extension method on a multidimensional array, which fails unless you use Cast<T> or similar; so I can definitely see the an argument for making multidimensional arrays implement IEnumerable<T>.

To clarify my question in code, I would expect the following code to print true four times, while it actually prints true, false, true, true:

int[] singleDimensionArray = new int[10];
int[,] multiDimensional = new int[10, 10];

Debug.WriteLine(singleDimensionArray is IEnumerable<int>);
Debug.WriteLine(multiDimensional is IEnumerable<int>);
Debug.WriteLine(singleDimensionArray is IEnumerable);
Debug.WriteLine(multiDimensional is IEnumerable);
1
  • 4
    Also ugly: multiDimensional is implicitly convertible to the non-generic type System.Collections.IList (simply because System.Array implements that interface). So you could say System.Collections.IList mdCast = multiDimensional;. Then using the one-parameter indexer on mdCast will fail only at runtime. See doc on MSDN. Note the exception type, ArgumentException. Really ugly. Commented Jun 11, 2012 at 12:46

5 Answers 5

55

The CLR has two different kinds of arrays: vectors which are guaranteed to be one-dimensional with a lower bound of 0, and more general arrays which can have non-zero bounds and a rank other than 0.

From section 8.9.1 of the CLI spec:

Additionally, a created vector with element type T, implements the interface System.Collections.Generic.IList<U> (§8.7), where U := T.

I have to say it seems pretty weird to me. Given that it already implements IEnumerable I don't see why it shouldn't implement IEnumerable<T>. It wouldn't make as much sense to implement IList<T>, but the simple generic interface would be fine.

If you want this, you could either call Cast<T> (if you're using .NET 3.5) or write your own method to iterate through the array. To avoid casting you'd have to write your own method which found the lower/upper bounds of each dimension, and fetched things that way. Not terribly pleasant.

Sign up to request clarification or add additional context in comments.

1 Comment

Also, the C# Language Specification (version 4.0) mentions in paragraph 6.1.6 only the conversion of a single-dimensional array to IList<> and its base interfaces. But it's a shame that a T[,] is not an ICollection<T>.
21

There is a workaround: you can convert any multidimensional array to an IEnumerable

public static class ArrayExtensions
{
    public static IEnumerable<T> ToEnumerable<T>(this Array target)
    {
        foreach (var item in target)
            yield return (T)item;
    }
}

5 Comments

Great stuff! I had to explicitly specify the generic type though, so usage becomes: myArray.ToEnumerable<myType>()
This is the same as IEnumerable.Cast<T>.
@Markus Brilliant, this should be the accepted answer. As an aside, if the array can contain empty values it becomes Matrix.Cast(Of T).Where(Function(x) x IsNot Nothing)
@smirkingman or IEnumerable.OfType<T>
This accesses the array through IEnumerator which causes boxing, as @SergeyTeplyakov said in his answer. Boxing doesn't happen if the array is explicitly typed (T[,], T[,,], etc.).
17

Zero bound single dimensional arrays implements both IEnumerable and IEnumerable<T>, but multi-dimensional arrays, unfortunately, implements only IEnumerable. The "workaround" by @Jader Dias indeed converts a multidimensional array to IEnumerable<T> but with a huge cost: every element of an array will be boxed.

Here is a version that won't cause boxing for every element:

public static class ArrayExtensions
{
    public static IEnumerable<T> ToEnumerable<T>(this T[,] target)
    {
        foreach (var item in target)
            yield return item;
    }
}

3 Comments

I’d be interested in an explanation about how this avoids boxing, given the foreach statement typically just calls GetEnumerator() under the hood.
@KyleMcClellan typically, but the compiler helps us lower those GetEnumerator() calls to index-based iteration. Check the decompiled code for a normal foreach(var element in simplestArray) and you will see. It is not boxing objects in every Current before giving us those back. This is the same for foreach(var element in twoDimensionalAray. It is using the [,] index based enumeration. What this answer does is to piggy-back on this compiler lowering magic which is transferred into the custom IEnumerable<T> implementation powering the method.
@KyleMcClellan your intuition would be right if we defined the method as public static IEnumerable<T> ToEnumerable<T>(this IEnumerable target)
0

Jagged arrays don't support IEnumerable<int> either, because multidimensional structures aren't really an array of a type, they are an array of an array of a type:

int[] singleDimensionArray = new int[10];
int[][] multiJagged = new int[10][];

Debug.WriteLine(singleDimensionArray is IEnumerable<int>);
Debug.WriteLine(multiJagged is IEnumerable<int[]>);
Debug.WriteLine(singleDimensionArray is IEnumerable);
Debug.WriteLine(multiJagged is IEnumerable);

Prints true, true, true, true.

Note: int[,] isn't an IEnumerable<int[]>, that's for the reasons specified in the other answer, namely there's no generic way to know which dimension to iterate over. With jagged arrays, there isn't as much room for interpretation because the syntax is pretty clear about it being an array of arrays.

3 Comments

Recursion isn't under discussion.
@recursive: cheers, fixed. Just a case of mixing up my terminology.
He does not talk about jagged arrays, he talked about a multidimensional array.
0

Think inversely. The 2d array already exists. Just enumerate it. Create a 2d array with score and place of an initial array or marks, including duplicate values.

int[] secondmarks = {20, 15, 31, 34, 35, 50, 40, 90, 99, 100, 20};

IEnumerable<int> finallist = secondmarks.OrderByDescending(c => c);

int[,] orderedMarks = new int[2, finallist.Count()];

Enumerable.Range(0, finallist.Count()).ToList().ForEach(k => {orderedMarks[0, k] = (int) finallist.Skip(k).Take(1).Average();
orderedMarks[1, k] = k + 1;}); 

Enumerable.Range(0, finallist.Count()).Select(m => new {Score = orderedMarks[0, m], Place = orderedMarks[1, m]}).Dump();

Results:

Score Place

100     1
99      2 
90      3 
50      4 
40      5 
35      6    
34      7    
31      8    
20      9     
20     10 
15     11 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.