Open MadsTorgersen opened 1 month ago
This proposal specifically does not attempt to preserve semantics from old to new syntax. This means that library authors concerned with compatibility may need to keep their classic extension methods around for a while, possibly forever
This is an important difference between the two proposals. The proposal I just opened strives for perfect semantic guarantees while allowing all extensions to move forward.
We should discuss as a group if that's important.
Personally, I think it is. Having a mishmash of old and new isn't going to feel good, and I'm unsure if we'll even convince users they can do so safely. This risk both the community and internal code bifurcation I call out in my speclet.
I would like people to feel that the can completely safely move to the new form without any risk, and then enjoy both the migrated methods and new members living together harmoniously.
Of note: much of the concerns raised here (attributes, refness, merging, etc.) simply fall out. So I personally think the other approach strikes a nicer "no compromises" approach. :-)
Maybe just forget about the new syntax completely and have it be implemented in the old-style, with static methods in static classes, but making the compiler recognize such methods as properties and operators, with something like this:
using System.Runtime.CompilerServices;
public static class Extensions
{
[SpecialName] public static int get_IntProperty(this TargetClass self) => 42;
[SpecialName] public static string op_Implicit(this TargetClass self) => self.ToString();
}
It turns out the above syntax for user-defined operators is already valid and recognized by the compiler, though not at design-time, only when it comes from metadata.
For the property case, you could have the compiler recognize the get_
or set_
naming convention + the SpecialNameAttribute
, and have the compiler emit a property to metadata as well.
And of course, adjust the process of symbol lookup to find these new fake properties and operators.
- Non-goal: Allow all existing extension methods to be faithfully ported to the new paradigm
This is slightly surprising. I assume it will still be true, even if it is not a goal?
I take this also means the "old syntax" will never really be deprecated, but perhaps the new approach will be encouraged, say, using an analyzer+code fixer?
@julealgon:
- Non-goal: Allow all existing extension methods to be faithfully ported to the new paradigm
This is slightly surprising. I assume it will still be true, even if it is not a goal?
All current extensions could be ported over to work the same (or very close) as extension methods. But they won't also show up as static methods that you can call directly.
I take this also means the "old syntax" will never really be deprecated, but perhaps the new approach will be encouraged, say, using an analyzer+code fixer?
Correct. The goal is that there's no reason to use the old syntax for new code going forward.
I currently expect to sometimes prefer the old syntax going forward no matter what new syntax becomes available. It's hard to beat adding/removing just the token this
at the point of deciding whether or not to make a static utility method an extension method.
Non-goal: Allow all existing extension methods to be faithfully ported to the new paradigm
This could be if the syntax could be relaxed a bit.
Since right now all extensions are non-generic (and static), to cover generic extensions, extension
could be always emitted as a non-generic type - type parameters would not be a part of the extension type and they can be partial
to cover multiple target types.
partial extension<T> Enumerable for IEnumerable<T> { // static class Enumerable;
public IOrderedEnumerable<T> OrderBy(...);
}
partial extension<T> Enumerable for IOrderedEnumerable<T> { // the target doesn't need to match
public IOrderedEnumerable<T> ThenBy(...);
}
This will emit the exact same Enumerable class that we have today.
With that I think it makes more sense to make the name optional for local usage where the name is not going to matter,
file extension for string {
// ..
}
For internal extensions that could suffice 99% of the time where you don't need to disambiguate.
Proposed additions 1 and 2 completely cement the "static class" approach for new extensions. Does that mean interface implementation is out the window? If not, what happens in this case?
extension FooExt for Foo : IBar
{
public void Method() { }
public void Func() // IBar method
{
this.Method();
}
public void OtherMethod()
{
this.Func();
}
public Action GetDelegate() => this.Func;
}
How is the above emitted? What about delegates' Target
?
Addition 3 makes sense in any case.
Regarding item 4 - one more reason to just stick to original plan.
Items 5 and 6 - I'm assuming this means the [OverloadResolutionPriority(...)]
would be used to prefer new extension's methods, where desired? My initial reaction is, this makes sense - the only downside being that it's likely lead to pollution with this attribute on a whole bunch of methods; there is an argument to be made that if new extensions are the way forward, we can just prioritize them over old ones.
Item 7 - why is this needed? This is completely against everything else in the language.
What about languages which don't (at least for some time) recognize C# extensions? If we make them static classes, then using them in other languages necessitates doing a static class invocation, which is fine, but it means changing to a struct wrapper is impossible.
I still think that way too much effort is being spent to ship extensions without the underlying runtime changes needed to deliver the full feature with interfaces. In the process it's getting more and more confusing.
Compatibility through coexistence between extension types and extension methods
This has been moved to https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/extensions/Compatibility%20through%20coexistence%20between%20extension%20types%20and%20extension%20methods.md.