# Collection literals working group meeting for August 11, 2023

## Agenda

* Implementations used when targeting interface types

## Discussion

Following up from the Aug 8th language design meeting, the working group considered specific concrete implementations to use for each interface that collection expressions can target. We reviewed the [Compiler-synthesized types](https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/Compiler-synthesized-types.md) proposal in preparation for upcoming language design meetings which will get into more of the details of the collection expressions feature.

The language design meeting on Aug 8th concluded that we can later change the concrete type created for interfaces even after people start using the feature. For now, we'll decide on a starting point to recommend which we feel comfortable with.

### Targeting mutable interfaces

When a collection expression targets `IList<T>` or `ICollection<T>`, we'll construct a `List<T>`. Even if people start depending on it, we could still change it in the future, similar to how .NET Core changed the concrete type returned from `Enumerable.Empty<T>()`.

### Targeting readonly interfaces

These are `IEnumerable<T>`, `IReadOnlyCollection<T>`, and `IReadOnlyList<T>`. Empty collections will get whatever instance the framework returns from `Array.Empty<T>()`. For non-empty collections targeting readonly interfaces, we considered three alternatives:

A: Synthesized type with a field of type `IList<T>` or `List<T>`  
B: Just use the BCL's `ReadOnlyCollection<T>` type  
C: Use `List<T>` for unknown-length construction and `T[]` for known-length.  

Alternative C is great for performance but loses the safety that is a selling point to a significant part of our audience. Alternative B adds in the possibility for folks to cast and depend on the exact type (even though we do reserve the right to change the concrete type anyway), but it also doesn't have some of the benefits of alternative A; there are things we could do better in private implementations such as sealing it and wrapping a `List<T>` field rather than `IList<T>`, gaining better codegen with devirtualization and inlining.

With the extra indirection, indexing through a readonly wrapper is measurably worse than on an unwrapped array, twice as slow in a benchmark. On the other hand, wrappers add no cost to enumeration via `foreach` and `IEnumerable<T>` over the wrapped collection because the wrapped collection's enumerator can be returned directly through the wrapper.

For known-length collections, it may be worthwhile to construct and wrap a `T[]` rather than a `List<T>`, or even synthesize interface implementations with hardcoded counts and hardcoded fields for elements for certain sizes. `T[]` wouldn't be great for unknown-length collections since arrays must be exactly sized, often requiring a final copy from a larger-sized buffer that was used to hold the items while the length wasn't yet known. We could wrap an `IList<T>` field to handle both, or we could generate different wrappers to contain a `List<T>` field or a `T[]` field for the two situations as needed.

Even if the target type is only `IEnumerable<T>`, the synthesized types should implement `IReadOnlyList<T>`, `IList<T>`, and nongeneric `IList`. This is in case of subsequent usage against runtime checks, such as data binding or LINQ optimizations. It could result in bigger generated code size that isn't trimmable. Method bodies could be shared though, since most of the `IList<T>` and nongeneric `IList` members are mutating members which contractually should just throw `NotSupportedException`.

## Conclusion

For `ICollection<T>` or `IList<T>`, we will build a new `List<T>`.

For (non-empty) `IEnumerable<T>`, `IReadOnlyCollection<T>`, or `IReadOnlyList<T>`, initially we will synthesize a private type within the assembly which is similar to the framework's `System.Collections.ObjectModel.ReadOnlyCollection<T>`. This is a straightforward starting point which prevents casting to the framework class. The design space is open and the compiler can create specialized classes later in a data-driven fashion.

In C# 13 when we get to dictionaries, `IDictionary<T>` will be a new `Dictionary<T>`, and `IReadOnlyDictionary<T>` will follow a strategy consistent with the strategy for `IReadOnlyList<T>`, such as synthesizing a private type similar to `System.Collections.ObjectModel.ReadOnlyDictionary<TKey, TValue>`.
