Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open issues for collection literals #7085

Closed
cston opened this issue Apr 1, 2023 · 8 comments
Closed

Open issues for collection literals #7085

cston opened this issue Apr 1, 2023 · 8 comments
Labels
Proposal Question Question to be discussed in LDM related to a proposal

Comments

@cston
Copy link
Member

cston commented Apr 1, 2023

Open issues for collection literals

Open issues for collection literals to review at LDM (see proposal).

Support spread operator

Support a spread operator to inline an enumerable within a collection literal. The proposed syntax is ..e

  • Possible translation of List<int> list = [x, ..e, y];

    // List<int> list = [x, ..e, y];
    List<int> __result = new List<int>();
    __result.Add(x);
    foreach (var __i in e)
        __result.Add(__i);
    __result.Add(y);
  • Avoid extra allocations when the enumerable type has a Length or Count property, or when TryGetNonEnumeratedCount<T>(this IEnumerable<T>, out int) returns true.

    List<int> __result = e.TryGetNonEnumeratedCount(out int n)
        ? new List<int>(capacity: 2 + n)
        : new List<int>();
    // add items
  • Avoid intermediate collections when not observable.

    For instance, avoid generating collections for the conditional expression b ? [1, 2, 3] : [] below.

    bool b = ...;
    var v = [x, .. b ? [1, 2, 3] : [ ], y];
  • Syntax ambiguity between spreads and ranges in a collection literal. (Since the ambiguity is within a collection literal, this is not a compatibility issue.)

    Range[] ranges = [range1, ..e, range2]; // ..e: spread or range?
    • Require parentheses (..e) or include a start index 0..e for a range, or
    • Choose a different syntax (like ...) for spread. (Lack of consistency with slice patterns.)

Support dictionaries and dictionary elements

Support collection literals that represent dictionaries, with a simple syntax for key-value pairs. The proposed syntax for key-value pairs is k:v

  • Possible translation of var d = [k1:v1, k2:v2];

    // var d = [k1:v1, k2:v2];
    var __result = new Dictionary<TKey, TValue>();
    __result[k1] = v1;
    __result[k2] = v2;
  • Interfaces I<TKey, TValue> implemented by Dictionary<TKey, TValue> can be used as target types for collection literals.

    IReadOnlyDictionary<string, int> d = [x:y, ..e];
  • The natural type of a collection literal is Dictionary<TKey, TValue> when the best common type of the elements is KeyValuePair<TKey, TValue>.

  • Construction of dictionary collection literals uses the indexer this[TKey] { get; set; } rather than Add(TKey, TValue), to provide consistent set semantics rather than add.

    // IReadOnlyDictionary<string, int> d = [x:y, ..e];
    var __result = new Dictionary<string, int>();
    __result[x] = y;
    foreach (var (__k, __v) in e)
        __result[__k] = __v;
    IReadOnlyDictionary<string, int> d = __result;
  • Syntax ambiguity in a collection literal between a conditional expression and k:v with conditional access. (Since the ambiguity is within a collection literal, this is not a compatibility issue.)

    var v = [a ? [b] : c]; // [a ? ([b]) : c)] or [(a ? [b]) : c]?

    Could bind to conditional access, based on precedence, and require parentheses for a ? ([b]) : c.

Support Construct methods

Allow collection types with custom Construct methods (see proposal).

  • This is primarily to allow efficient construction of immutable collections, and requires BCL changes to expose Construct methods on existing immutable collection types.

    ImmutableArray<T> result = [x, ..e, y]; // ImmutableArray<T>.Construct(T[] values)
  • A type T can be constructed from a collection literal through the use of a void Construct(CollectionType) method when:

    • the Construct method is found on an instance of T (including extension methods?), and
    • CollectionType is some other type known to be a constructible type.
  • To implement: ImmutableArray<T> result = [x, ..e, y];

    Implementing explicitly, using a builder:

    var __builder = ImmutableArray.CreateBuilder<T>(initialCapacity: 2 + n);
    
    __builder.Add(x);
    __builder.AddRange(e);
    __builder.Add(y);
    
    // Create final result. __builder is now garbage.
    ImmutableArray<T> __result = __builder.MoveToImmutable();

    Possible translation from collection literal using init void ImmutableArray<T>.Construct(T[] values) method:

    T[] __storage = new T[2 + n];
    int __index = 0;
    
    __storage[__index++] = x;
    foreach (var __t in e)
        __storage[__index++] = __t;
    __storage[__index++] = y;
    
    // Create final result. __storage owned by __result.
    var __result = new ImmutableArray<T>();
    __result.Construct(__storage); // init void ImmutableArray<T>.Construct(T[] values)
  • A type with suitable Construct method can be used as a target type for collection literals.

  • May require an init Construct() method to ensure collection instance is not mutated after construction.

  • Could we use constructors or factory methods instead?

    For instance, with support for params ReadOnlySpan<T>:

    public struct ImmutableArray<T>
    {
        public ImmutableArray(params ReadOnlySpan<T> values) { ... }
    }
@333fred 333fred added the Proposal Question Question to be discussed in LDM related to a proposal label Apr 2, 2023
@333fred
Copy link
Member

333fred commented Apr 3, 2023

@sab39
Copy link

sab39 commented Apr 7, 2023

Is there a reason why an API like the one used for InterpolatedStringHandler wouldn't work for collection literal construction? It seems to me at first glance that the needs are very similar: the compiler knows a certain amount, but not necessarily everything, about how much space will be needed; there is intermediate mutable state being used to construct an ultimately immutable object in a performance-critical way without copying; the mutable data must be unreachable after construction is complete. Several of the proposed approaches to Construct support for collection literals seem at first glance like they're partially reinventing the same wheel.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Apr 7, 2023

Is there a reason why an API like the one used for InterpolatedStringHandler wouldn't work for collection literal construction?

There's no reason why it wouldn't work. Howeve, the core question would be: is such complexity necessary? The ISH case has to deal with much more complex scenarios (including not wanting to do any work depending on the state of things at runtime). Collection literals are much simpler and thus can benefit from a much narrower and easier pattern around construction.

@Thaina
Copy link

Thaina commented Apr 15, 2023

I wish I could use this feature at initializer for list and dictionary

var sarray = "0,1,2,3,4,5,6,7,8,9";
var list = new List<int>() {
   1,
   ..sarray.Split(',').Select((x) => int.TryParse(x,out var n) ? n : -1).Where((n) => n > 1 && n < 9),
   9
}; // result in 1,2,34,5,6,7,8,9

For dictionary, it should accept list of KeyValuePair or tuple

var sarray = "0,1,2,3,4,5,6,7,8,9";
var dict = new Dictionary<string,int>() {
   ["1"] = 1,
   ..sarray.Split(',').Select((x) => KeyValuePair.Create(x,int.TryParse(x,out var n) ? n : -1)).Where((pair) => pair.Value > 1 && pair.Value < 9),
   ["9"] = 9
};

This also allow us to make Dictionary initialize with conditional key existence

@CyrusNajmabadi
Copy link
Member

THe feature already allows for that, just with shorter syntax. Specifically:

var list = [
   1,
   ..sarray.Split(',').Select((x) => int.TryParse(x,out var n) ? n : -1).Where((n) => n > 1 && n < 9),
   9
]; // result in 1,2,34,5,6,7,8,9

// and

var dict = [
   "1": 1,
   ..sarray.Split(',').Select((x) => KeyValuePair.Create(x,int.TryParse(x,out var n) ? n : -1)).Where((pair) => pair.Value > 1 && pair.Value < 9),
   "9": 9
];

@Thaina
Copy link

Thaina commented Apr 15, 2023

@CyrusNajmabadi Is the dict will become Dictionary<K,V> or just KeyValuePair[] though?

I would love to specified the type by myself

@CyrusNajmabadi
Copy link
Member

@Thaina you can see the rules in the linekd proposal: https://github.com/dotnet/csharplang/blob/main/proposals/collection-literals.md

I would love to specified the type by myself

You can :)

@Thaina
Copy link

Thaina commented Apr 15, 2023

Thank you very much

@333fred 333fred closed this as completed Nov 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Proposal Question Question to be discussed in LDM related to a proposal
Projects
None yet
Development

No branches or pull requests

5 participants