-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: extension everything #6136
Comments
It seems pretty straightforward. It also seems to cover everything except two items, which I'm hoping can be addressed by the eventual solution.
|
How about:
Could an extension class share instance fields between extension members?
|
This is a good idea. It neatly solves the problem the design team has faced of how to add static extension methods with a decent syntax. The fact that the current extension method syntax then becomes obsolete is probably not a bad thing either- as I've always found the current syntax to be overly subtle and not particularly well designed. |
I like this a lot - neatly ties up a lot of the extension concepts floating around. What about extending interfaces (obviously without static members), or would this syntax be exclusively for classes? I.e.: interface IA
{
string ExistingMethod(int a);
}
internal /* or public */ extension interface IA /// or "class IA" ???
{
public void Method()
{
ExistingMethod(42);
}
public string Property
{
get
{
return "test";
}
set
{
ExistingMethod(value);
}
}
// etc..
} Perhaps the declaration syntax proposed by @bbarry ( What about |
I don't know, I like the explicitness of the current extension method syntax as accepting the instance via a parameter. With this syntax the compiler would need to add the I also have concerns about the concept of extension properties (and events) given that they're not just accessor methods but also the member metadata which can't be amended on the target class. Both relatively minor points. Beyond that it does seem like a decent way to provide extension members. Thoughts as to how the class would be emitted by the compiler? What is the name of the extension class? internal static class `A.extension` {
[Extension] // property to indicate a constructor?
public static A `.ext_ctor`(int someSpecialConstructor)
{
A tmp = new A();
tmp.ExistingMethod(someSpecialConstructor);
return tmp;
}
[Extension]
public static void Method(A `this`)
{
`this`.ExistingMethod(42);
}
[Extension(typeof(A))] // property to indicate extended type?
public static void StaticMethod()
{
A.ExistingStaticMethod();
}
[Extension] // property to indicate a property?
public static string get_Property(A `this`)
{
return "test";
}
[Extension]
public static void set_Property(A `this`, string value)
{
`this`.ExistingMethod(value);
}
} |
On method rewriting- this is already done extensively with async and iterator methods, so it doesn't seem like a big jump to apply a similar concept elsewhere. I'm not sure I follow the second point correctly, but an implementation of extension properties could use the |
Rewriting the method body, sure, but the signature is always left intact. My second point isn't about extension "fields" or state, which is a set of problems unto itself. It's that while "extension properties" might fake syntax of feeling like a property that they can't be properties where reflection metadata is concerned making them useless in many of the scenarios used for properties like serialization or data binding. That may be a worthwhile tradeoff to have the syntax but it could also be quite confusing to have something that smells like a property not be able to be used like a property. |
What if lowering was done like this; given this extension class:
Generated code looks something like this:
I'm not convinced extension properties are a good thing either and feel the same for extension constructors. I think static extension methods and extension operators have merit in the "let's play nicer with functional programming language style" meta-goal but I am not sure of the others. "My" suggested syntax wasn't mine to begin with either, @chrisaut suggested it in #112 (comment) (I suspect this issue is a dupe). |
I think explicit cast operators to interface adapters could be defined as well:
now any class that derives from
|
I have been thinking along the similar lines. But was thinking more like: internal /* or public */ extension StringExtensions : String
{
// etc..
} Where the "inheritance" bit is what defines what the extension is targeting. That way you can define it where you like (namespace etc.) and only include it where necessary via a Not sure I am keen on being able to add state to existing types this way though. |
@HaloFour These are good points, but I think both seem minor in the scheme of things. I wouldn't expect an extension property (or anything else) to be any more available by type reflection than an extension method is today, and the difference could theoretically be highlighted in the IDE by colorization (although at present the colorization provided to C# by VS is still stuck in the 1990s). It seems like it would be pretty unusual that you'd be actually reflecting over the extension class itself. I think adding state to existing types is an important use case. For example, WPF's attached properties are very useful but at the same time extremely kludgy. A language-based solution would have been much preferable. As we know extension properties already almost made it into the language but for failing to meet this use case. We should also take a cue from the JavaScript/TypeScript world, where bolting new behavior and state onto types is fairly common. |
We've been thinking about this and calling it "extension everything", so I updated the title. |
WPF is an Entity Component System which has been designed with a special use case in mind (Styling, property inheritance etc.). Each DependencyObject is a collection of it's own state. Having object state stored in a static collection in another class seems like GC/performance issue just waiting to happen. JavaScript objects are also a collection of there own state. JavaScript allow bolting-on because it is a dynamically typed language with that design in mind. C# already has this covered with dynamic dispatch and types like ExpandoObject. TypeScript is a super-set of JavaScript and uses same runtime so same comments are applicable here. I think coming up with a one size fits all (or most) solution for this would be difficult. |
@dfkeenan Additional state (e.g. a field) is something we are not likely to support in a feature like this. |
Any chance of supporting extension virtual/abstract methods? They might have an unwanted side effect of breaking your code when you reference an assembly with them, and they might require CLR changes, since there's no place in the vtable left for them, but they are or so useful. |
@dfkeenan Take a look at the |
@gafter It seems like extension properties would be mostly useless if you cannot store additional state with them. |
I would be in favor of supporting additional state as part of an extension class for any reference type. Support for this functionality has been available in the CLR since .NET 4.0 as part of supporting various dynamic languages. It's not always the most efficient approach, but it's reasonable to think for certain applications it would make implementing new functionality much easier. |
@orthoxerox Not without the CLR team's help. We have higher priority things we'd ask for. @MgSam @sharwell There are lots of things (computed but not stored) extension properties are useful for. The use of |
@gafter Is it not true that extension properties were previously designed and jettisoned because they couldn't meet the needs of WPF's attached properties? Clearly the team at some point thought it was important to have them store state.
Yes, it's true you could hack together your own extension property that stores state. But you can already do that today with extension methods. If extension properties, constructors, etc. add nothing over existing extension methods save a slightly nicer invocation syntax then it hardly seems worth the considerable design effort to add them into the language. |
@MgSam I heard it was an issue of syntax, going with something that looked like property syntax except further decorated. A generic extension indexer ended up mixing three kinds of brackets in one construct. WPF was always going to use its own state mechanism so it wouldn't have mattered if the language included a concept of extension state and if the language tried to force it most of the functionality of attached dependency properties wouldn't have worked anyway. |
@MgSam |
Interesting. Thanks for the explanation. Though I do think that underlying implementation details should probably be secondary considerations when deciding the merits of a feature (if there's a will, there's a way). That being said, IMHO I think extension everything is near the bottom of the "features I'd really like to see" list, and not worth doing at all if additional state is not part of the deal. |
@alrz Your timing could be better. Couldn't you have wondered that some six or seven years ago? You would have saved us an enormous amount of work 😉
|
How do you scope the use of the extension? What if 2 libraries extend the same class? What if they have member name clashes? or both extensions implement the same Interface? Although I don't think the CLR could currently support extensions that implement Interfaces. Though that would be quite cool. I thought was one of the nifty features of Swift. After asking these questions I started to wonder how do you disambiguate extension properties/indexers? Would the generated methods just be available as static methods with nice names. i.e. a property called |
@dfkeenan Very valid question. Conflicts as such are not the issue. Resolving them is the issue. Such conflicts can arise with extension methods too, when you reference two libraries/namespaces that might define an extension with the same name and signature on the same type. When that happens the compiler does not complain until you call the extension methods, in which case there will be ambiguity. In that case, as a workaround, you could call the extension method via its static class and pass the extended type instance as a parameter and resolve the ambiguity. A syntax proposal could then be (see also #3357):
(
against:
Casting of course would not be needed when there is no ambiguity. Something interesting to keep in mind is that there might be room for inheritance in the extending class, i.e. the extending class might be able to inherit from some other class (not only interfaces). Which means that multiple inheritance might be achievable with extension classes given that there is clear separation between the hierarchy of the original class and the hierarchy of the extension. |
"Casting" might be a good way to disambiguate. Not sure I like the syntax for the extension itself though. Maybe take a page out of the generics book and us the public interface ITotal
{
int TotalLength { get; }
}
public extension ThatsALotOfString<IEnumerable<string>> : ITotal
{
public int TotalLength { get { return this.Sum( s => s.Length); } }
} or maybe: public extension ThatsALotOfString<IEnumerable<T>> : ITotal
where T : string
{
public int TotalLength { get { return this.Sum( s => s.Length); } }
} |
I am not sure I understand what class is being extended. |
Well in my example it's |
I like the idea of go style to make extension of everything and let go back to use pure struct along with interface But I would go against making class for extend things Instead we should extend everything with the extension method. May tweak some syntax for extension constructor. But all should be inside static class as the same. Or no class at all (Yes, back to the old function only like C++) |
Since this is the main issue for extensions I'd like to note I'm curious to see how extension constructors would look like, perhaps they have to return an object? If so can we cover #6788 with it? |
Copy of Partial ? |
How would you be able to do partial if you don't own the source of the thing you want to extend? |
@Joe4evr |
@vbcodec: "nearly" is the important word: There are only extension methods, but no extension properties, extension events, extension static methods, or extension constructors. |
We are now taking language feature discussion in other repositories:
Features that are under active design or development, or which are "championed" by someone on the language design team, have already been moved either as issues or as checked-in design documents. For example, the proposal in this repo "Proposal: Partial interface implementation a.k.a. Traits" (issue 16139 and a few other issues that request the same thing) are now tracked by the language team at issue 52 in https://github.com/dotnet/csharplang/issues, and there is a draft spec at https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md and further discussion at issue 288 in https://github.com/dotnet/csharplang/issues. Prototyping of the compiler portion of language features is still tracked here; see, for example, https://github.com/dotnet/roslyn/tree/features/DefaultInterfaceImplementation and issue 17952. In order to facilitate that transition, we have started closing language design discussions from the roslyn repo with a note briefly explaining why. When we are aware of an existing discussion for the feature already in the new repo, we are adding a link to that. But we're not adding new issues to the new repos for existing discussions in this repo that the language design team does not currently envision taking on. Our intent is to eventually close the language design issues in the Roslyn repo and encourage discussion in one of the new repos instead. Our intent is not to shut down discussion on language design - you can still continue discussion on the closed issues if you want - but rather we would like to encourage people to move discussion to where we are more likely to be paying attention (the new repo), or to abandon discussions that are no longer of interest to you. If you happen to notice that one of the closed issues has a relevant issue in the new repo, and we have not added a link to the new issue, we would appreciate you providing a link from the old to the new discussion. That way people who are still interested in the discussion can start paying attention to the new issue. Also, we'd welcome any ideas you might have on how we could better manage the transition. Comments and discussion about closing and/or moving issues should be directed to #18002. Comments and discussion about this issue can take place here or on an issue in the relevant repo. This may be close enough to dotnet/csharplang#192 that discussion can continue there. |
I removed the comment "I am not moving this particular issue because I don't have confidence that the LDM would likely consider doing this.". In fact, the LDM is considering doing this. See dotnet/csharplang#192 |
Easiest explanation in the example:
The text was updated successfully, but these errors were encountered: