Closed gafter closed 7 years ago
Why we still require to name the extension class while it can be self explanatory like:
public extension int { }
// instead of
public extension class IntegerExtensions : int { }
Using class-base to specify the target type can get weird in case of generic, static or array types:
public extension class IntegerArrayExtensions : int[] { }
// for defining static extension methods ..
public extension class ConsoleExtensions : Console { }
// would this be allowed?
public extension class GenericExtensions<T> : T /* where T : ... */ { }
Alternatively,
public extension int[] { }
public extension Console { }
public extension<T> T /* where T : ... */ { }
public extension<T> List<List<T>> { }
This would reserve class-base for when we have virtual extension methods (#258) to add interface implementations to existing types (#8127).
I apologize if I misunderstood the proposal, but I don't think the way it treats this
makes much sense.
return this._flattenCount;
So, static
extension members are accessed from static
extension members using this.
, which looks like instance member access? I think that's very confusing.
The additional hidden parameter is named
@this
from theSemanticModel
's point of view.
So, to keep a static
cache of the last instance some member was called on, I would have to write the following?
this._cache = @this;
Again, quite confusing.
@alrz I think the name can be useful. You can use it to explicitly call the extension method as a normal method (e.g. Enumerable.ToList(query)
). And it also allows you to select only some extension types from a namespace (e.g. using static System.Linq.Enumerable;
).
@svick
I apologize if I misunderstood the proposal, but I don't think the way it treats this makes much sense.
It was messed up. Static members are accessed by qualifying with the extended type. I've fixed up the OP.
So, to keep a static cache of the last instance some member was called on, I would have to write the following?
this._cache = @this;
Again, quite confusing.
The @
is to show that it is an ordinary identifier when viewed through the symbol table (compiler API), and that you can access it that way if you want. You can still refer to it using the this
keyword.
@gafter Ok, now it looks good to me.
@svick Currently you can not use using static
for extension methods. Also they can't be defined in generic classes. As you can see in the original post ListListExtensions<T>
is a generic class (I'm not saying that this can't be done in the new syntax, though).
You can use it to explicitly call the extension method as a normal method
If that's your concern you'd better use the existing extension syntax (that'd be the benefit for the old syntax to be not totally deprecated). But I don't expect to be able to use extension methods as if they are static methods when I explicitly defined them in an extension
declaration as non-static members!
And it also allows you to select only some extension types from a namespace
I didn't suggest that they be in the scope out of nowhere, just like what we have with extension methods, the containing namespace must be imported using using
.
I think naming classes like extension class WhateverExtensions : Whatever
is just too repetitive.
@alrz
Currently you can not use
using static
for extension methods.
This compiles fine for me:
using static System.Linq.Enumerable;
class Program
{
static void Main()
{
new int[0].ToList();
}
}
@svick Oh right. But this doesn't ToList(new int[0]);
assuming using static System.Linq.Enumerable;
.
While I do agree that naming an extension class is useful, I think that (formally) deriving this class from the extended class makes little sense overall.
public class ClassToExtend
{
public ClassToExtend (int arg) { ... }
}
public extension class ExtensionClass : ClassToExtend
{
// For when constructors are supported.
public ExtensionClass (int arg1, int arg2) { ... }
}
...
// Unlikely the desired syntax, but should make sense if
// ExtensionClass is a descendant of ClassToExtend.
ClassToExtend a = new ExtensionClass(1, 2);
// Likely the desired syntax.
ClassToExtend b = new ClassToExtend(1, 2);
// Totally not what we need.
ExtensionClass c = ...
In short, extension class is not a descendant of the extended class, so it shouldn't look like one. The syntax could rather go like this:
public extension class ExtensionClass<T> for ClassToExtend<T> { ... }
@quinisext
I do agree that naming an extension class is useful,
Can you give some examples, how is that useful? I also note that if your extension class is generic, which it very may well be, you can't use using static
as @svick mentioned. This is not a problem right now because extension methods are generic, not the enclosing class, so using static
is fine. Even if you could "use extension methods as if they are static methods even though you explicitly defined them as non-static members" which makes little sense, type inference won't help you and you will need to explicitly specify type arguments. So for complete compatibility with existing extension classes, generated class must not have a type parameter list and perhaps with an optional name, because it's not always useful.
@alrz
Even if the name is optional, I believe the class
keyword should be obligatory.
public extension class<T> for T[] { ... }
(I'm not insisting on for
, of course, just not :
).
It makes the code clearer to read (especially when extension
is not highlighted by the editor) and more uniform, and most probably easier to parse. Without it, the extension
would probably need to be promoted to a full-fledged keyword status. Naming makes the code more uniform too, though.
Actually i'd rather have as little new syntax/keywords as possible:
// Only a for-part is something new; and no new keywords, contextual or not.
public class ArrayExtension<T> for T[] { ... }
// Easier to get as a special case of the previous. Still no good looking.
public class for ExtendedClass { ... }
public class<T> for T[] { ... }
Names would allow to using static
an extension type without using
the rest of its namespace.
Names are useful to me personally, due to the way I use reflection (which I do extensively) (can be achieved through attributes though).
Names would allow to create two distinct extension classes in one namespace, which can be using static
separately.
They're also future-proof, in case some day language gets parametrized usings.
using static SomeNamespace.ExtensionClass<System.String>;
Admittedly these all are pretty esoteric.
Still the syntax you proposed does seem appealing to me, I'm just afraid it would complicate parsing, and the word "extension" is too good a word to make it a keyword. Maybe something like
public extending<T> T[] { ... }
Interesting :+1:
Which existing types in CoreFx or third parties could benefit from the feature?
:
, is a bad idea. It already has too many meanings, don't add another one.@MgSam;
- Spec seems interesting and reminiscent of some previous community suggestions, however, I personally think you don't get much value-add over existing extension methods unless you add the ability to have data associated with an instance (extension instance properties).
Isn't this supposed to be static extensions? Why is "add the ability to have data associated with an instance (extension instance properties)" important?
- Agree with previous comments that re-using the extends operator, : , is a bad idea. It already has too many meanings, don't add another one.
That's when Java and Visual Basic start to look good for having extends and implements keywords. But for C#, I think it's too late to go that way. Another punctuation?
@paulomorgado My point was this seems like a lot of design work for very little benefit over current state if adding state to the instance is not on the table. Having extension methods on static classes are the only truly new use cases in this spec, but even that is less useful than it once was with C# 6.0's using static
feature. (You can just dump all the static classes you want into scope in your file)
Agree the ship has sailed long ago on extends
and implements
, but that doesn't mean we should perpetuate the mistake by reusing :
yet again. Make it an English-language keyword word rather than punctuation.
@MgSam I use extension methods extensively (sorry), so I miss such feature for quite some time (it is in Delphi/Free Pascal for a long time now). It allows adding properties using standard syntax, and that is enough benefit for me on its own.
@quinisext I use extension methods extensively too. The only difference this proposal makes for properties is that instead of having extension methods SetFoo
and GetFoo
, you now can make those a property. But they still can only address static data. That doesn't seem like it's worth the design cost to me.
On the other hand, if there is a realistic way to associate instance data, then it becomes a big win. WPF has the whole concept of "attached properties", which are absolutely nasty to declare, yet this proposal still doesn't fill the huge niche that those attempted to address. The C# design team tried to tackle extension everything once before and rejected it because the design was incompatible with this use case.
I consider "extension static members" including "extension operators" the most important benefits of this.
@MgSam I'm actually more interested in readonly properties. But having properties you can always implement your own mechanism of attaching them:
public class ExtensionClass for System.Windows.Controls.Button // Whatever would be the syntax.
{
// You would still need to deal with abandoned values, but there is probably no good way
// to solve this problem in general case.
private static Dictionary<MyClass, int> values;
public MyIntProperty
{
get { return value[this]; }
set
{
values[this] = value;
}
}
// Some neat tricks.
public double CanvasLeft
{
get { return Canvas.GetLeft(this); }
set { Canvas.SetLeft(this, value); }
}
public RoutedEventHandler ClickHandler
{
get { return Click; }
set { Click += value; }
}
// Extension constructor.
public ExtensionClass (out Button variable)
{
var result = new Button();
variable = result;
return result;
}
}
// Now also this coud work.
var button = (Button)null;
var Panel = new System.Windows.Controls.Canvas
{
Children =
{
new Button (ref button)
{
CanvasLeft = 20,
MyIntProperty = 42,
ClickHandler = (s, e) => { DoSomething(); }
}
}
};
button.Click += MyOtherHandler;
You can't do these things with Set/GetSomething().
Extension constructors may work well with immutable data. And extension operators would allow you to connect mathematical types that was initially unaware of each other.
If the concept of "extended" state is to be on the table I would like to see something come out of the CLR that would enable such functionality without incurring the penalties associated with ConditionalWeakTable
. Otherwise my opinion is that C# shouldn't encourage such use and that if people want the "extended" state then they can simply follow those patterns manually.
Beyond that, extension static methods, properties and operators do seem like they'd be worth it. Extension constructors are weird but I can see their value. Maybe that could lead to asynchronous constructors #6788.
@quinisext That's a memory leak. Correct implementation would need something like WeakReference Dictionary. I'd rather see language support for extension fields than implementing this (I would probably get it wrong). EDIT: I have just found out that there is something like WeakReference Dictionary in .NET already - System.Runtime.CompilerServices.ConditionalWeakTable class. It's only downside is restricting values to reference types.
@zippec
That's a memory leak.
That's exactly what I meant by
You would still need to deal with abandoned values.
As I said, I don't see a good general solution, that can be used as an implementation for extension fields/auto-properties. But that was not the point of the example. The point was to show that extension classes are worth implementing even without that particular feature. As to the feature, it would be nice to have it somehow, but is there a really good way to make it? If you do it manually, you at least see what's going on and understand the costs and limitations.
Another idea for the syntax - just use the type that we are extending, possibly with additional constraints.
public extension class List<T> {..}
public extension class List<T> where T: List<int> {..}
public extension class List<T> where T: class {..}
public extension class List<T> where T: int {..}
@VSadov
What would the name be for the generated static class if the developer doesn't explicitly provide one? Whatever it is would have to be predictable in order for a recompilation to not break existing consumers, and it would have to be resilient to collisions in case you want to extend two different classes named List
, or with a different arity of generic parameter types.
@VSadov Would it scale well?
public extension class int[] { ... } // Formally works but unnatural for c#.
Also, as HaloFour said, what is the "predictable" name for int[]
extension class? It is just unobvious in general case.
The challenge here is to have a syntax very similar to a class definition, but yet we want to sneak in a type reference to a potentially more constrained instantiation of a type - like List< int >.
My suggestion may not even be better than what @gafter proposed. Just another idea how we can abuse preexisting syntax.
Having no name is ok. Not everything in the source ends up with formal names. As implementation, compiler can generate something unspeakable and specify applicability of the extension container via attributes.
@VSadov
Unspeakable would be perfectly fine as long as the name is determinstic based on the extended type.
:spaghetti:
Extended | Generated Name * |
---|---|
int[] |
'System.Int32[]<>Extensions' |
List<T> |
'System.Collections.Generic.List 1<>Extensions'` ** |
List<int> |
'System.Collections.Generic.List 1 |
* Note that the namespace is embedded in the class name. ** Would the static type itself be generic, or would only the static extension methods be generic?
@HaloFour What if you extend the same type twice in the same namespace (most likely from different files)? E.g.:
public extension class List<T>
{
public void ExtensionMethod1() {}
}
public extension class List<T>
{
public void ExtensionMethod2() {}
}
The only solution I can think of would be to combine both extensions into the same class.
@gafter Yes, yes, yes! I love this proposal. It would go a long way to allow layered designs without sacrificing on discoverability. :) We struggled with it consistently in the evolution of .NET Core and this would let us restore source compatibility to some of the harder decisions that were made. For example, Delegate.CreateDelegate
and Char.GetUnicodeCategory
could be made to work again.
// We do not support instance extension fields, but we could support them in the future // if the type being extended is a value type, using ConditionalWeakTable.
Should this read "... if the type being extended is not a value type, ..."?
@VSadov @HaloFour Maybe I'm missing something, but if the container is unspeakable than there will be no syntax for forcing a call to an extension over a first-class member defined on the target, or to disambiguate between competing extensions. It would also seem to leave any .NET language that has not caught up to the metadata conventions backing this with no way to invoke these members.
@nguerrera That's a good argument for requiring the developer to explicitly name the extension class.
@VSadov
The challenge here is to have a syntax very similar to a class definition
I think it doesn't make sense to do that because extension
is essentially a different kind rather than just a modifier. For example, take Module
in VB, it turns everything to a Shared
member (and Shared
itself is not permitted), it's not a modified Class
it's a different kind. In contrast, we require to use static
on members in a static class, so there is nothing magical about static
modifier. On the other hand, extension
does permit static
modifier on members, it's a separate kind in any way.
Note that with your suggestion it's not possible to declare an extension class for a generic T
.
Another thing about existing extension classes is that they are always non-generic classes so I'm thinking for maximum compatibility e.g. rewriting existing extension classes as extension
declarations, it should generate a non-generic class, regardless of the extension itself being generic.
Would be nice if extension members took structs by ref
, not by copy.
@omariom
Question here would be if #165 is adopted then would extension classes always expose the this
argument as ref
if the extended type is a value type?
Re: unspeakable name.
Yes. After thinking about it, it seems that sooner or later there will be a need to invoke an extension directly. That would require the container be referable by name, so my suggestion would not work.
I am convinced.
Rust's attaching methods to the data is kind of similar.
@HaloFour I think it should. And while this is new feature anyway we could have it better than we already have
And I could say that we most likely to use extension method on struct. Because normal class could be extended as a derived class but struct cannot. But to have extension method only pass by value stop it. And it is design flawed all along that it can't fix because backward compatible
So now we can fix it with new syntax then it is a must
Still I'm not agree with operator. Because we can't find reference of duplicate operator like we can find member
@gafter Would it not be possible to support getter-setter properties without supporting fields? There is still some debate about the impact of ConditionalWeakTable, but supporting getters/setters:
@masaeedu If you can't have a field, where would you store the ConditionalWeakTable
?
@gafter Static field, and I'd do the bookkeeping for associating instances with their data explicitly.
@masaeedu Yes, if we support static
fields then you can use ConditionalWeakTable
to simulate instance fields for reference types.
You would need to box simulated fields to maintain right semantincs. Fields are variables - can be passed byref and so on.
So it would actually need to be something like ConditionalWeakTable<TContainer, StrongBox<TField>>
It would not be very efficient, but yes, extension fields are possible in theory.
I think we should not making extension field automatically like that. We may want to implement it with our own
@gafter Why would you not support static fields? If you don't, the workaround would be to create a new static class to hold the fields which is a bit ugly. Especially for encapsulation.
@gafter Will this "we do not permit adding interfaces in the base clause" be allowed at some point? I assume it wouldn't, I know that it requires CLR support but besides this is there any reason not to allow it?
@eyalsk It can get out of hand,
// lib 1
interface A {}
interface B {}
// lib 2, references: lib 1
class C : A {}
// lib 3, references: lib 1 and 2
void F(A a) {}
void F(B a) {}
F(new C());
The last line compiles but if you reference another library which happen to have an extension for C that implements B, it suddenly breaks. In Rust, however, either the target type or the trait must be declared in the same crate. I suppose this can be enforced in C# as well. See #8127.
@alrz Thank you. :)
I apologize if this syntax has been suggested before and I somehow missed it. This suggestion requires no changes to the C# grammar, and works for pretty much any kind of member you want to add. In fact, this example below is valid C# 3.5.
I like the fact that this syntax is explicit, that these declarations look exactly like what they are: static methods of a static class to which the compiler is redirecting.
I'm not a big fan of unnamed "extension classes" in C#. This may work well for other languages but I don't think it's appropriate for a CLR language designed to interoperate with other languages. The first problem is how these extension classes will appear when consumed outside of C# or by previous versions of the language. Next, there is ambiguity between what is a member of the extension class, and what is a member of the class being extended. And finally, there are many features of ordinary classes that need to be restricted in an "extension class" context: protection levels, finalizers, virtual/override/new, fields, interface implementations, and probably more. The cognitive load here is quite high--it's another whole kind of type to learn.
So, there is no magic happening in this syntax; no automatic weak-reference dictionary for properties or events. You're free to do that on your own, or perhaps the framework could provide something a little more friendly than ConditionalWeakTable. I think this is the best way to handle the situation because many types expose their own property/event dictionaries (Control, DependencyObject, etc) and are best extended in this way.
Like extension methods today, it is possible to declare and consume these extension members using any .NET language (even those that do not support extension methods/members) and with any CLR version.
Here is the code:
// Property getters and setters
// Compiler restricts the number of arguments and return types.
// When accessed as a property, the "Get" and "Set" prefixes are omitted just like the
// "Attribute" suffix is omitted from attribute classes.
// There is no compiler-generated storage mechanism and no "auto" extension properties.
[Extension(ExtensionType.GetProperty)]
public static string GetTrimmedText(this Control control)
{
return control.Text.Trim();
}
[Extension(ExtensionType.SetProperty)]
public static void SetTrimmedText(this Control control, string value)
{
control.Text = value.Trim();
}
// Event accessors
// Compiler restricts the number of arguments and the return type must be void.
// When accessed as an event, the "Add" and "Remove" prefixes are omitted.
[Extension(ExtensionType.AddEvent)]
public static void AddMouseTripleClick(this Control control, MouseEventHandler e)
{
// ... etc
control.Events.AddHandler("MouseTripleClick", e);
}
[Extension(ExtensionType.RemoveEvent)]
public static void RemoveMouseTripleClick(this Control control, MouseEventHandler e)
{
// ... etc
control.Events.RemoveHandler("MouseTripleClick", e);
}
// Static extension members are declared without a "this" argument, and by providing
// an additional parameter to the ExtensionAttribute to say which type will receive the member.
// This usage should work for all kinds of members (not just methods).
[Extension(typeof(Color))]
public static Color FromHwb(double hue, double whiteness, double blackness, double alpha)
{
// convert HWB to RGB ...
return Color.FromArgb(...);
}
// Extension constructor
// The return type determines the type for which the constructor is being declared.
// We will probably want some restrictions here for enum types, static classes, etc.
// The name of this member is not used, it is called like `new ReturnType(...)`.
[Extension(ExtensionType.Constructor)]
public static IWidget CreateNativeWidget()
{
// choose implementation, then...
return new XWidget();
}
// Extension operator
// Compiler restricts the number and types of arguments and return types.
// We will probably want restrictions for enum types, static classes, generics, etc.
// The name of the member is not used, it is called like an operator.
[Extension(ExtensionType.OperatorAdd)]
public static Vector3 Add(Vector3 left, Vector3 right)
{
return new Vector3(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
}
@gafter I am sorry if I am getting more theoretical at a time where more concrete ideas are discussed but I think the following can shed some light on the issues involved.
It helps a lot if we think that Type Extensibility is equivalent to a construct where a normal every-day type (the Extension Type) has a non-optional weak reference to an instance of the type it extends (the Extended Type). This binding of the two types needs to be bundled up in such a way that syntactically an instance of the Extension Type is identified with an instance of the Extended Type, and all the visible members of the Extension Type can be called via that identification.
This identification gives us three features (the first two of which to me are the core purpose of Type Extensibility):
myExtendedTypeInstance.MyExtensionTypeProperty
), this includes static members as well.(IMyExtensionTypeInterface)myExtendedTypeInstance
or (ExtensionTypeBase)myExtendedTypeInstance
).
[EDIT: These casts should be implicit where appropriate](IMyExtendedTypeInterface)myExtensionTypeInstance
).Looking at it this way, an extension class can be like any other normal class with its own inheritance chain (both up and down the hierarchy), interface implementations, generic type-variables etc. The only difference with a normal class is that there need to be some limitations due to the binding with the Extended Type (for example there needs to be some limitation in naming members to avoid collisions with members of the Extended Type).
My point is that all scenarios (features and limitations) can be properly studied by implementing them in today's C# language features, via the equivalence described above (the two linked types and a weak reference between them). If a scenario is possible and logically consistent today by this equivalence, it can be and should be included in the Type Extensibility features of the language.
I also agree with @HaloFour that this needs to be done via the CLR, not just compiler deception. I go even further as to state that Type Extensibility should be part of legitimate, academic OOP theory that needs to be properly formalised and that OOP languages should be making an effort to implement it.
From this point of view I suggest the following syntax for the class definition (note the keyword extensionof
):
public class MyExtensionType : MyExtensionTypeBase, ISomeInterface,
extensionof ExtendedType { }
or
public class MyExtensionType extensionof ExtendedType : MyExtensionTypeBase, ISomeInterface { }
or
public class MyExtensionType extensionof<ExtendedType> : MyExtensionTypeBase, ISomeInterface { }
Alternatively extensionof
can be used with <>
or ( )
.
The point of this syntax is that other than the extensionof<ExtendedType>
signal, the definition of MyExtensionType
is like any normal class. And this signal just tells the compiler that this class needs a weak reference to an instance of the Extended Type.
Another proposal is the keyword extension
to point to the instance of Extended Type that the extension refers to from within the MyExtensionType
class (similar to the base
or this
keyword).
Constructors and Operators are quite interesting because they are members that are shared between the Extension and Extended Type (i.e. unlike an extension property their call is identical e.g. new ExtendedType(some parameters...)
or myExtendedTypeInstance1 + myExtendedTypeInstance2
). This means that some element of overridden and altered behaviour from the original could potentially be introduced.
Given the possibility that the original Extended Type constructors and operators can be called from the Extension Type (e.g. an Extended Type constructor could be called via the extension
keyword similarly to base
and this
constructors calls), three general principles need to be considered:
The first gives more freedom and leaves the developers (both creators and consumers of extensions) to deal with consequences. The advantage here is that the option to preserve is still available.
The second means constructor and operator extensions are not allowed.
The third means for example that when a constructor whose signature matches an original constructor is called then the original constructor is force-called to create the referenced Extended Type instance but no access to that instance is given during construction to avoid altering state and behaviour (i.e. the extension
reference will not be available). New constructors and operators (that don't match original ones) are freely allowed in this case.
Without totally rejecting 1 and 2 it seems that 3 is closer to the idea behind Type Extensibility i.e. extending a pre-existing type's state and functionality without interfering with its original behaviour.
Case studies can be i) the Visual Studio designer working with extended controls or ii) the compiler and framework working with extended primitives (e.g. extended strings) or iii) the framework working with extended classes in general or iv) an attempt to recreate WPF in WindowsForms with extended controls.
Hope this is of some help.
There are existing proposals/requests #3357, #4945, #5165, #5624, #6136, but this is a slightly different take. One difference is that qualification with
this
would be required inside, at least for access to extension members.Extension Everything for C
This proposal gives a way to define new kinds of extension members. Today it is possible to define methods that act as if they are instance methods of the extended type. This proposal expands that capability, supporting both static and instance members, and supporting (or at least discussing) all of the kinds of members you might want to declare.
Here is an example that summarizes the new proposed syntax forms:
Limitations:
_flattenCount
above) work the same as extension members, even though not accessible outside.Substantial LDM design work will be required
e.M
to the set of extensions that might be designated by the access.