dotnet / csharplang

The official repo for the design of the C# programming language

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Open issue: relax requirement that type be enumerable to participate in collection expressions

jcouv opened this issue · comments

It is presently not possible to declare a collection type that doesn't implement some IEnumerable interface.
We should consider allowing types marked with [CollectionBuilder(...)] to not be enumerable (and thus not have an iterator type).

I ran into this issue trying to make a roslyn type (CSharpTestSource) usable with collection expressions. This required a dummy implementation of an IEnumerable interface despite that interface playing little role for compiling this code.

To achieve this, we would remove the requirement from the [CollectionBuilder] section:

The collection type must have an iteration type.

And we would instead use the type from the parameter type of the Create method, ie. E in ReadOnlySpan<E>. All elements of the collection need to an implicit conversion to that type.

Relates to #5354
FYI @cston @CyrusNajmabadi

Not sure how I feel about this.

Why not make it enumerable?

Seems very weird that you found use a collection to create it, but then not be able to iterate it.

Here's the roslyn example that prompted this issue: dotnet/roslyn#71134
Having a dummy/unused IEnumerable implementation seems superfluous.

I guess the same feelings existed for collection expressions :-). I'll look at the example though!

I see. Imo the better fix at the language level is for implicit operators to participate in collection expression target typing.

If you have an implicit operators from array, then this should ideally just work.

We explicitly (no pun intended) excluded that when we did not make a collection expression conversion a standard implicit conversion.

We explicitly (no pun intended) excluded that when we did not make a collection expression conversion a standard implicit conversion.

Yup yup. But I'm curious if we might reconsider given some real world use case.

That said, CSharpSources seems, to me, to indicate a collection type. So not being enumerable is frankly kinda strange. I'll have to look and see how you guys actually end up using this.

I'll have to look and see how you guys actually end up using this.

We basically always realize it to a flattened array when we actually go to access it.

so you have a collection type which is like an array, but it's all wonky on conversions to/from an array. go tit. :)

not sure how i feel about this case. seems very different than basically like everything else out there. note that i view collection-exprs as a common way to create collections. Not a nice syntax to create any arbitrary type. But i am open to this sort of relaxation because... well... it doesn't seem like it would harm anything.

I thought one intended use of CollectionBuilder was to allow creation of ref structs from collection-exprs. Is that not the case? SharpLab

using System;
using System.Runtime.CompilerServices;

[CollectionBuilder(typeof(RS), nameof(RS.Create))]
public ref struct RS {
    public RS Create(ReadOnlySpan<int> items) => throw null!;
    public static void M() {
        RS rs1 = [1, 2, 3]; // error CS9188: 'RS' has a CollectionBuilderAttribute but no element type.
    }
}

I thought one intended use of CollectionBuilder was to allow creation of ref structs from collection-exprs. Is that not the case?

It should be possible to create a ref struct collection with a pattern-based GetEnumerator() method (see sharplab.io).

[CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))]
public ref struct MyCollection<T>
{
    private readonly List<T> _list;
    public MyCollection(List<T> list) { _list = list; }
    public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();
}

There was a community request for this exact issue description at #7398 (reply in thread).

Also, it would enable Memory<T> (which has no GetEnumerator) to add [CollectionBuilder] and be targeted by collection expressions without being special-cased by the compiler.