dotnet / csharplang

The official repo for the design of the C# programming language
11.62k stars 1.03k forks source link

[Proposal]: Compatibility through coexistence between extension types and extension methods #8496

Open MadsTorgersen opened 1 month ago

MadsTorgersen commented 1 month ago

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.

CyrusNajmabadi commented 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. :-)

BlinD-HuNTeR commented 1 month ago

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.

julealgon commented 1 month ago
  • 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?

MadsTorgersen commented 1 month ago

@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.

jnm2 commented 1 month ago

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.

alrz commented 1 month ago

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.

TahirAhmadov commented 1 month ago

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.