Back to Csharplang

C# Language Design Meeting for May 5th, 2025

meetings/2025/LDM-2025-05-05.md

latest8.6 KB
Original Source

C# Language Design Meeting for May 5th, 2025

Agenda

Quote of the Day

  • "If you change your source from release to release, which people typically do"

Discussion

Extensions

Champion issue: https://github.com/dotnet/csharplang/issues/8697
Spec: https://github.com/dotnet/csharplang/blob/555aeea68ef3ce29c313ddd68d4a0fdd68d84420/proposals/extensions.md

XML Docs

Questions: https://github.com/dotnet/csharplang/blob/555aeea68ef3ce29c313ddd68d4a0fdd68d84420/proposals/extensions.md#xml-docs
Related: https://github.com/dotnet/csharplang/blob/555aeea68ef3ce29c313ddd68d4a0fdd68d84420/meetings/working-groups/extensions/Extension-API-docs.md

We started today by reviewing the proposed structure of XML doc comments. This review raised a few new questions as we confirmed various components. First, should we permit overriding the param documentation for the receiver parameter on individual members? Our common example, Enumerable.cs, does have different this parameter documentation throughout the file. The source of elements to be cast vs The source of elements to be filtered, etc. However, we don't know that this is critical functionality, or that Enumerable.cs would have wanted to take advantage of this if it was being rewritten from scratch today. There is already a workaround in declaring a new extension block. This is the same workaround that we advise for other things that cannot be overridden, such as nullability annotations, attributes, this parameter names, etc. Therefore, we are ok with simply adding the documentation comment to the list of things that cannot be overridden on individual members, for now, and can revisit later if the demand for this proves itself.

Related, we also thought about the summary on the extension blocks themselves. In particular, we're not sure that this block would actually appear anywhere in the IDE or docs experience. We want to revisit this support and make sure that we actually have a use case for it before shipping.

Another related issue we briefly mentioned is missing documentation comment warnings; we want to make sure that adding parameter comments to one member won't virally create missing parameter documentation comments on all the rest of the members in an extension block; the example here would be adding parameter documentation to one member, but not having documentation on the extension parameter. The user then gets a warning that the extension parameter is missing a comment. Then, when the user adds that comment, now every other member in the block that has additional parameters is missing comments on those parameters. We need to cut off that cycle somewhere to strike a usability balance, which will we think about and come back with.

Finally, we raised a question of how users can cref an extension property itself. The current design allows referring to either the getter or setter by using the disambiguation syntax, but has no way to refer to the entire property itself. We will need to design this and come back with a proposal.

Conclusion

XML approach is generally approved, with a few open questions around extension block summaries, warning virality, and property crefs.

Participation in pattern-based constructs

Question: https://github.com/dotnet/csharplang/blob/555aeea68ef3ce29c313ddd68d4a0fdd68d84420/proposals/extensions.md#pattern-based-constructs

Next, we took a look at what contexts we want new extensions to work in. For methods, the answer is straightforward: everywhere they work today. For properties, it's more subtle. We wanted to start by refining the "why" of our current rules, so we can apply that why to properties and have it naturally fall out. Roughly speaking, we generally allow extensions where the extension is not about state in the underlying object. For example, we allow an extension GetAwaiter, which returns a new object that can track the state of an underlying object and implement the await pattern. However, we do not then allow an extension GetResult on the object returned by GetAwaiter, because our expectation is that the Result is intrinsic to the awaiter's state. Similarly, we do not allow extension Dispose, because that is intrinsic to the state of an object. We acknowledge, however, that this principle is not universally applied. We're not sure that we'd exclude Slice methods implemented as extensions today, for example, and GetPinnableReference is very related to an object's state. Given this, we'll also draw from any related patterns in a given area.

We are ok with the list of areas as proposed here: allowing them in object/dictionary/with initializers, and property patterns. For now, we think we should not include Count/Length properties in the determination of whether or not an object is countable, meaning they won't participate in list patterns or in implicit Range/Index indexers. We think this because of our prior art around Slice, and while we think we might want to revisit that entire space, we want to do that as a whole item, not an individual piece.

We are not settled on when delegate-returning properties will be accepted. We have conflicting prior art; LINQ works as a syntactic rewrite, and thus delegate-returning properties are accepted without question. Other areas are not syntactic rewrites, and do not accept delegate-returning properties. More thought needs to go into the space before we can make a decision.

Conclusion

New extensions methods will behave like old extension methods in pattern-based locations. Extension properties will participate in in object/dictionary/with initializers and property patterns for now, with further expansion on the table for later.

Skeleton type metadata

Question: https://github.com/dotnet/csharplang/blob/555aeea68ef3ce29c313ddd68d4a0fdd68d84420/proposals/extensions.md#namingnumbering-scheme-for-skeleton-type

Public API tools that verify our unspeakable extension metadata have an issue with how we currently decide on names. Our current approach has some issues around determinism that we need to address, but even if we adjust this, it seems very likely that the public API validation tools are going to need to adjust. We don't think that we consider the names of the skeleton structures to be part of the API; they're not referred by implementations, and purely exist for the compiler to be able to understand underlying structure. We don't think there's a naming structure that we can come up with that won't run into collisions of some kind unless we have an incrementing number somewhere, which would then break if something is reordered. Given this, we believe the tool will still need to update to understand when the name isn't important.

Conclusion

The compiler algorithm does need to be double-checked and verified, but we expect tooling that validates public APIs from release to release will need to update as well.

Single-phase consequences

Question: https://github.com/dotnet/csharplang/blob/555aeea68ef3ce29c313ddd68d4a0fdd68d84420/proposals/extensions.md#new-generic-extension-cast-method-still-cant-work-in-linq
Related: https://github.com/dotnet/roslyn/issues/78415

Finally, we looked at an outcome of moving back to single-phase generic resolution. The BCL had been hoping to fix an issue with Cast<TSource, TResult> on IAsyncEnumerable, where since TResult cannot be inferred from the parameters, both generic type parameters must be explicitly supplied, so things like from Type t in asyncEnumerable, which gets translated into asyncEnumerable.Cast<Type>(), cannot work due to differing arity. The first thing we considered was whether LINQ could do something about this, potentially specifying both type parameters in this case. We think that would be especially difficult given the current structure of query expressions; they're defined as pure syntactic rewrites, not involving semantics. This fallback would have to be based on semantic information.

We also briefly talked about two-phase inference again. We do think that this scenario is something that we want to support in the language, but as in previous LDMs, we don't think that this limitation is specific to extensions. Instead, we want to investigate a more general approach that can work regardless of extension-ness.

Conclusion

This is not the straw that breaks the camel's back. We will continue to investigate what we can do to make this scenario better in general.