meetings/2025/LDM-2025-02-19.md
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:
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:
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.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.