Skip to content

Instantly share code, notes, and snippets.

@jnm2
Last active October 8, 2023 20:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jnm2/3302653e89eb631e1342f73c553cb56e to your computer and use it in GitHub Desktop.
Save jnm2/3302653e89eb631e1342f73c553cb56e to your computer and use it in GitHub Desktop.
Open types in nameof

Open types in nameof

[TODO: follow standard sections, add examples, flesh everything out]

Motivation

This is a small feature that removes a common frustration: why do I have to pick a generic type argument when the choice has no effect on the evaluation of the expression? It's very odd to require something to be specified within an operand when it has no impact on the result. Notably, typeof does not suffer from this limitation.

It's not just about code that better expresses itself. Once some arbitrary type argument has been chosen in a nameof expression, such as object?, changing a constraint on a type parameter can break uses of nameof unnecessarily. Insult becomes added to injury in this scenario. Satisfying the type parameter requires declaring a dummy class to implement an interface which is constraining the type parameter. Now there's unused metadata and a strange name invented, all for the purpose of adding a type argument to the nameof expression, a type argument which nameof will ultimately ignore even though it requires it.

In some rarer cases, with a generic class constraint, it's not even possible to use nameof because it's not possible to inherit from a base class which is used as a generic constraint, due to the base class having an internal constructor or internal abstract member.

There's a lot of bang for the buck in fixing this one. When you hit this, it feels like a paper cut. It's gotten steady attention from the community, including an initial proposal by Jon Skeet. Implementation complexity is very low. An LDM member and a community have each implemented this feature in the compiler for purposes of investigation.

Description

Unbound type names become available for use with nameof:

  • nameof(A<>) evaluates to "A"
  • nameof(Dictionary<,>) evaluates to "Dictionary"

Additionally, chains of members will be able to be accessed on unbound types, just like on bound types:

class A<T>
{
   public List<T> B { get; }
}
  • nameof(A<>.B) evaluates to "B"
  • nameof(A<>.B.Count) evaluates to "Count"

Even members of generic types can be accessed, consistent with how nameof already works when the type is not unbound. Since the type is unbound, there is no type information on these members beyond what System.Object or any additional generic constraints provide.

class A<TItem, TCollection> where TCollection : IReadOnlyCollection<TItem>
{
   public TCollection B { get; }
}
  • nameof(A<,>.B) evaluates to "B"
  • nameof(A<,>.B.Count) evaluates to "Count".

Not supported

  1. Support is not included for nesting an unbound type as a type argument to another generic type, such as A<B<>> or A<B<>>.C.D. Even though this could be logically implemented, such expressions have no precedent in the language, and there is not sufficient motivation to introduce it:

    • A<B<>> provides no benefits over A<>, and A<B<>>.C provides no benefits over A<>.C.

    • If C returns the T of A<T>, A<B<>>.C.D can be written more directly as B<>.D. If it returns some other type, then A<B<>>.C.D provides no benefits over A<>.C.D.

  2. Support is not included for partially unbound types, such as Dictionary<int,>. Similarly, such expressions have no precedent in the language, and there is not sufficient motivation. That form provides no benefits over Dictionary<,>, and accessing members of T-returning members can be written more directly without wrapping in a partially unbound type.

Detailed design

Member lookup on an unbound type expression will be performed the same way as for a this expression within that type declaration.

No change is needed for the cases listed in the Not supported section. They already provide the same errors for nameof expressions as they do for typeof.

No change is needed when the syntax nameof binds to a method named 'nameof' rather than being the contextual keyword nameof. Passing any type expression to a method results in CS0119: '...' is a type, which is not valid in the given context. This already covers unbound generic type expressions.

Spec updates

TODO

Open questions

None.

Design meetings

https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-11-06.md#roslyn-20450

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment