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

Compiler implementation strategy for dictionary expressions targeted to IReadOnlyDictionary<TKey, TValue> #78101

Open
CyrusNajmabadi opened this issue Apr 10, 2025 · 2 comments
Assignees
Labels
Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead

Comments

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Apr 10, 2025

The implementation strategy for the compiler for C# 14 when targeting a dictionary expression against IReadOnlyDictionary<TKey, TValue> should be very close to as follows:

Note: ReadOnlyDictionary<TKey,TValue> (and its Dictionary-taking constructor) is available on:

Product Version
.NET Core 1.0, Core 1.1, Core 2.0, Core 2.1, Core 2.2, Core 3.0, Core 3.1, 5, 6, 7, 8, 9, 10
.NET Framework 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1, 4.7.2, 4.8, 4.8.1
.NET Standard 1.0, 1.1, 1.2, 1.3, 1.4, 1.6, 2.0, 2.1

As such, the type and constructor can effectively always be depended on as a reliable backstop for emitting. We can error if we need this type and it is not available.

Required:

  1. [] should be emitted as ReadOnlyDictionary<,>.Empty when available (.Net 8+). Or a <private_impl>.ReadOnlyDictionary<TKey, TValue>.Empty = new(new()) static field we emit, if not available.
  2. [...] should emit using a FrozenDictionary<,> when assigned to a static readonly field, and when that type is available. The compiler should pass the keys/values in the collection expression to the appropriate factory method. Exact factory method TBD, but we expect there to be one that takes a ReadOnlySpan<KeyValuePair<,>> that we can look for.
  3. [...] should otherwise emit as new ReadOnlyDictionary<,>(Dictionary<,> __tempStorage). Where the compiler places the values into __tempStorage first, using its indexer, and then wraps that with a ReadOnlyDictionary<,>.

Optional:

Future optimizations are allowed in this space. For example, emitting [...] as as some specialized impl if the count of elements is fixed and very small. For example, a dict optimized for the single element case. And perhaps a specialized 2-N element dictionary that just has things in linear storage when N is very small. This would need good measurements to ensure the user value was there both when creating the dictionary and when consuming it.

Any specialized impl would have to abide by the contract of IReadOnlyDictionary<TKey, TValue> as well as the additional rules and recomendations placed by the language specification. Specifically:

If a type is synthesized, it is recommended the synthesized type implements IDictionary<TKey, TValue> as well. This ensures maximal compatibility with existing libraries, including those that introspect the interfaces implemented by a value in order to light up performance optimizations.

In addition, the value must implement the nongeneric IDictionary interface. This enables collection expressions to support dynamic introspection in scenarios such as data binding.

  1. The value must return true when queried for ICollection<T>.IsReadOnly. This ensures consumers can appropriately tell that the collection is non-mutable, despite implementing the mutable views.
  2. The value must throw on any call to a mutation method (like IDictionary<TKey, TValue>.Add). This ensures safety, preventing a non-mutable collection from being accidentally mutated.

--

Note with(...) support for IReadOnlyDictionary<,> is yet to be finalized. However, we will very likely have support for with(IEqualityComparer<TKey> comparer) for the user to supply a custom comparer. This should be possible with the above translation rules, just passing the comparer along appropriately.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Apr 10, 2025
@RikkiGibson
Copy link
Member

RikkiGibson commented Apr 10, 2025

fwiw, I would be completely fine with "use the well-known ReadOnlyDictionary always, and error if it is not available", for the purposes of shipping the feature. Under the assumption that any configuration which lacks that type is an unsupported one for this language version anyway. Then allow any/all of the listed refinements to come as time permits.

@CyrusNajmabadi
Copy link
Member Author

@RikkiGibson i think that is reasonable :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

No branches or pull requests

4 participants