Back to Csharplang

C# Language Design Meeting for February 19th, 2025

meetings/2025/LDM-2025-02-19.md

latest3.7 KB
Original Source

C# Language Design Meeting for February 19th, 2025

Agenda

Quote of the Day

  • "But, SHOULD we throw the baby out with the bath water?"

Discussion

Extensions

Champion issue: https://github.com/dotnet/csharplang/issues/8697
Specification: https://github.com/dotnet/csharplang/blob/e25de8eaaa30470ecd1202c1b1ce840512b0ff0c/proposals/extensions.md
Reviewed document: https://github.com/dotnet/csharplang/blob/e25de8eaaa30470ecd1202c1b1ce840512b0ff0c/meetings/working-groups/extensions/extensions-lookup.md

Today, we went over continuing proposals for how to do extension method lookup. This topic is both persistent and difficult, particularly since how extension method lookup was originally specified is not how it was actually implemented. As originally specified, extension methods invoked in extension form would have been subject to a 2-phase lookup approach, where first the receiver is used to find compatible extension methods, and then overload resolution would be performed among those methods. As the LDM, we generally prefer this approach; it is the most consistent with how lookup works for actual instance members. It would also ensure that "weird" results, like the out examples shown in extensions-lookup, behave as a user might naively expect, failing to unify because the extension is being called on I<string>, not I<object>. This potentially gets even more odd for static scenarios:

cs
var e = IEnumerable<string>.M(out object o); // What type is e? Does this line compile?

public static class Ext
{
    extension<T>(IEnumerable<T>)
    {
        public static IEnumerable<T> M(out T t) => ...;
    }
}

However, in C# 3.0, this is not what was implemented. The examples in extensions-lookup do not error, they compile, giving IEnumerable<object> as the receiver type. Given the 20 years of history here, and how any change to lookup is nearly certain to break someone, we don't feel comfortable breaking existing extension methods, and may even want to simply update the specification to cover what was really implemented in C# 3.0, rather than what was originally intended. Further, every attempt we've made during the design process of new extensions at breaking compatibility has led to us scaling back the breaks. At this point, we're aware of 3 major categories of extension methods that cannot be ported to the new syntax form 1-1 given the current design:

  1. Generic extensions that have a receiver that uses one or more of the type parameters and, at the call site, specify the type arguments. This is because they must specify all the type arguments, including any for the receiver, and that's not possible with the current design, as the receiver type parameters cannot be specified.
  2. Generic extensions that have a receiver that uses one or more of the type parameters and specify their type parameters in a non-idiomatic order. For example, a hypothetical TReturn Select<TReturn, TThis>(this TThis t, Func<TThis, TReturn> selector). We are not aware of real-world examples of this pattern, and are more comfortable ignoring it.
  3. The out inference issue that was brought up today.

We are, at the very least, concerned about categories 1 and 3. Given this, we want the working group to investigate what a compatibility mechanism would look like that handles these cases, potentially by changing how lookup works for such members, and whether we can get to a place that needs no explicit compatibility switch, but instead bakes the compatibility into the language.