dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.07k stars 4.69k forks source link

Support enumerating multidimensional arrays #49269

Open ekolis opened 3 years ago

ekolis commented 3 years ago

Description

Try to run this code:

using System;
using System.Linq;

var arr = new string[10, 10];

arr[0, 0] = "Fred";
arr[0, 1] = "Francine";
arr[1, 0] = "George";

var fs = arr.Where(q => q.StartsWith("F"));

foreach (var f in fs)
    Console.WriteLine(f);

Expected behavior: Fred and Francine are printed.

Actual behavior: Compile error on the LINQ call: Error CS1061 'string[*,*]' does not contain a definition for 'Where' and no accessible extension method 'Where' accepting a first argument of type 'string[*,*]' could be found (are you missing a using directive or an assembly reference?)

Configuration

.NET 5 Windows 10 Home 64 bit x64

Regression?

I'm pretty sure this worked in .NET Framework.

Other information

Workaround: call Cast on the array to convert it to an IEnumerable<T>:

var fs = arr.Cast<string>().Where(q => q.StartsWith("F"));
dotnet-issue-labeler[bot] commented 3 years ago

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

stephentoub commented 3 years ago

I'm pretty sure this worked in .NET Framework.

Nope ;-) Multidimensional arrays have always implemented IEnumerable, not IEnumerable<T>.

ghost commented 3 years ago

Tagging subscribers to this area: @eiriktsarpalis See info in area-owners.md if you want to be subscribed.

Issue Details
### Description Try to run this code: ```csharp using System; using System.Linq; var arr = new string[10, 10]; arr[0, 0] = "Fred"; arr[0, 1] = "Francine"; arr[1, 0] = "George"; var fs = arr.Where(q => q.StartsWith("F")); foreach (var f in fs) Console.WriteLine(f); ``` Expected behavior: Fred and Francine are printed. Actual behavior: Compile error on the LINQ call: `Error CS1061 'string[*,*]' does not contain a definition for 'Where' and no accessible extension method 'Where' accepting a first argument of type 'string[*,*]' could be found (are you missing a using directive or an assembly reference?)` ### Configuration .NET 5 Windows 10 Home 64 bit x64 ### Regression? I'm pretty sure this worked in .NET Framework. ### Other information Workaround: call `Cast` on the array to convert it to an `IEnumerable`: ```csharp var fs = arr.Cast().Where(q => q.StartsWith("F")); ```
Author: ekolis
Assignees: -
Labels: `area-System.Linq`, `untriaged`
Milestone: -
ekolis commented 3 years ago

I'm pretty sure this worked in .NET Framework.

Nope ;-) Multidimensional arrays have always implemented IEnumerable, not IEnumerable<T>.

Oh, really? Could have sworn they implemented the latter before! Wonder why they don't, anyway?

jkotas commented 3 years ago

IEnumerable<T> for arrays is implemented via "runtime magic". It is not pay-for-play in all scenarios and it is pretty complex, and it would be even more complex for multidimensional arrays. If we had a time machine and can do this again, there is a good chance we would pick a different design, e.g. static IEnumerable<T> AsEnumerable<T>(this T[] array) extension method.

eiriktsarpalis commented 3 years ago

If we had a time machine and can do this again, there is a good chance we would pick a different design, e.g. static IEnumerable AsEnumerable(this T[] array) extension method.

Is that something we could consider for multi-dimensional arrays? F# just introduced multi-dimensional array slicing so perhaps it makes sense to add enumeration support for submatrices (either by row or by column) by passing in a pair of Range parameters (in the case of 2D arrays).

andrei-faber commented 2 years ago

@joktas It is not pay-for-play in all scenarios and it is pretty complex - isn't it the same for IEnumerable?

jkotas commented 2 years ago

IEnumerable is not generic interface. It is implemented on System.Array without any runtime magic.

IEnumerable<T> is generic. It is what makes the implementation very complex for arrays.

If we were to do something here, I think it should be set of AsEnumerable-style extension methods that create enumerable view of the multidimensional array.

andrei-faber commented 2 years ago

@jkotas It is implemented on System.Array without any runtime magic. - I wonder where it's implemented for multidimensional arrays?

jkotas commented 2 years ago

I wonder where it's implemented for multidimensional arrays?

The non-generic implementation is shared between single-dimensional and multi-dimensional arrays: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Array.cs#L2406-L2409 and https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Array.Enumerators.cs#L10-L65

Rekkonnect commented 2 years ago

I think this issue would be better named as "Allow multidimensional arrays to implement IEnumerable<T>".

Onto the real things, I think a MDGenericArrayEnumerator<T> would be possible, just like SZGenericArrayEnumerator<T>. As for the implementation details, if I recall correctly, MD arrays are also stored sequentially, so a Span<T> would work, provided the Length property. It could also require a new Span<T>(Array) constructor. Overall, MD arrays need some work be done to come in par with SZ arrays.