Language Feature: Extension Everything #11159

Closed
gafter opened this Issue May 7, 2016 · 58 comments

Comments

@gafter
Member

gafter commented May 7, 2016

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:

// "extension" is a new (contextual keyword) modifier, permitted before the keyword "class".
// It can be combined with "partial". It produces a static class that one would not normally use
// directly, however there will be syntax forms for directly using all of the named members
// (but not the operators) as members of this class.
public extension class ListListExtensions<T>
  : List<List<T>> // the "extends" clause names the extended type. May be an interface
  // , Interface  // we do not permit adding interfaces in the base clause
{
   // static variables are no problem, and hosted in the container ListListExtensions<T>
   private static int _flattenCount = 0;

   // static methods are simply hosted in the container, ListListExtensions<T> in this case
   public static int GetFlattenCount()
   {
     return _flattenCount;
   }

   // static properties are similarly hosted in the container.
   // They translate into a static "indexed" property, indexed with a "this" (extension) parameter.
   public static int FlattenCount
   {
       get
       {
         return _flattenCount; // extension static variable access
       }
   }

   // instance methods are compiled using the same pattern as existing instance extensions.
   // The additional hidden parameter is named @this from the SemanticModel's point of view.
   public List<T> Flatten()
   {
     // Within instance members, "this" is of type List<List<T>>
     _flattenCount++;
     ...
   }

   // Extension indexers permitted, both static and instance.
   public List<List<T>> this[int[] indices] => ...;

   // We do not support instance extension fields, but we could support them in the future
   // if the type being extended is a reference type, using ConditionalWeakTable.
   // public int InstanceMember;

   // We do not support constructors, but we could support them in the future
   // by creating an ordinary method with a special name. Some design work would
   // be required to ensure the following syntax is possible, or to design an alternative.
   // public List<List<T>>(int defaultSize) : this(...) { ... }

   // Operators are permitted. Operators are required to have a parameter (or return type,
   // for implicit conversions) of the extended type.
   // All of the lookup rules for operators will need to be amended to look in extension types
   // that are in scope.
   public static implicit operator List<T>(List<List<T>> self)
   {
     return self.Flatten();
   }

   public static implicit List<List<T>> operator +(List<List<T>> left, List<List<T>> right) => left.Concat(right);
}

Limitations:

  • Instance fields not permitted (at first)
  • Constructors not permitted (at first)
  • Events not permitted (at first)
  • The base clause cannot add interfaces
  • No members may be virtual, override, abstract, sealed, etc.
  • Private helper members (e.g. _flattenCount above) work the same as extension members, even though not accessible outside.
  • Operators will require some language constraints on built-in types, interfaces, etc. For example, the extended type must be either the source or target of a conversion operator.
  • No instance auto-properties (until we support instance fields).

Substantial LDM design work will be required

  • What is the syntax?
  • What is allowed and not allowed? Extending int[]?
  • We will need to describe how we go from a member access e.M to the set of extensions that might be designated by the access.
  • Every construct that can use one of these will have to have its spec amended
    • Member access, method overload resolution
    • Indexing
    • Every kind of operator (e.g. what are the constraints at the declaration, where do we look on the use)
    • Later, constructors
  • For conversions, what limitations do we need to not run into trouble?
@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz May 7, 2016

Contributor

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

Contributor

alrz commented May 7, 2016

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

@svick

This comment has been minimized.

Show comment
Hide comment
@svick

svick May 7, 2016

Contributor

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 the SemanticModel'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.

Contributor

svick commented May 7, 2016

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 the SemanticModel'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.

@svick

This comment has been minimized.

Show comment
Hide comment
@svick

svick May 7, 2016

Contributor

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

Contributor

svick commented May 7, 2016

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

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter May 7, 2016

Member

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

Member

gafter commented May 7, 2016

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

@svick

This comment has been minimized.

Show comment
Hide comment
@svick

svick May 7, 2016

Contributor

@gafter Ok, now it looks good to me.

Contributor

svick commented May 7, 2016

@gafter Ok, now it looks good to me.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz May 7, 2016

Contributor

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

Contributor

alrz commented May 7, 2016

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

@svick

This comment has been minimized.

Show comment
Hide comment
@svick

svick May 8, 2016

Contributor

@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();
    }
}
Contributor

svick commented May 8, 2016

@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();
    }
}
@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz May 8, 2016

Contributor

@svick Oh right. But this doesn't ToList(new int[0]); assuming using static System.Linq.Enumerable;.

Contributor

alrz commented May 8, 2016

@svick Oh right. But this doesn't ToList(new int[0]); assuming using static System.Linq.Enumerable;.

@quinisext

This comment has been minimized.

Show comment
Hide comment
@quinisext

quinisext May 8, 2016

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> { ... }

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> { ... }
@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz May 8, 2016

Contributor

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

Contributor

alrz commented May 8, 2016

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

@quinisext

This comment has been minimized.

Show comment
Hide comment
@quinisext

quinisext May 8, 2016

@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[] { ... }

@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[] { ... }
@omariom

This comment has been minimized.

Show comment
Hide comment
@omariom

omariom May 8, 2016

Interesting 👍

Which existing types in CoreFx or third parties could benefit from the feature?

omariom commented May 8, 2016

Interesting 👍

Which existing types in CoreFx or third parties could benefit from the feature?

@MgSam

This comment has been minimized.

Show comment
Hide comment
@MgSam

MgSam May 9, 2016

  • 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).
  • 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.
  • @gafter You've previously told me in another one of these threads that using ConditionalWeakTable for associating data with an instance slows down the entire program, even where it's not being used. Is this not still the case? Why would extension instance properties be limited to value types?

MgSam commented May 9, 2016

  • 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).
  • 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.
  • @gafter You've previously told me in another one of these threads that using ConditionalWeakTable for associating data with an instance slows down the entire program, even where it's not being used. Is this not still the case? Why would extension instance properties be limited to value types?
@paulomorgado

This comment has been minimized.

Show comment
Hide comment
@paulomorgado

paulomorgado May 9, 2016

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

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

@MgSam

This comment has been minimized.

Show comment
Hide comment
@MgSam

MgSam May 9, 2016

@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 commented May 9, 2016

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

@quinisext

This comment has been minimized.

Show comment
Hide comment
@quinisext

quinisext May 9, 2016

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

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

@MgSam

This comment has been minimized.

Show comment
Hide comment
@MgSam

MgSam May 9, 2016

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

MgSam commented May 9, 2016

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

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter May 9, 2016

Member

I consider "extension static members" including "extension operators" the most important benefits of this.

Member

gafter commented May 9, 2016

I consider "extension static members" including "extension operators" the most important benefits of this.

@quinisext

This comment has been minimized.

Show comment
Hide comment
@quinisext

quinisext May 9, 2016

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

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

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour May 9, 2016

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.

HaloFour commented May 9, 2016

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.

@zippec

This comment has been minimized.

Show comment
Hide comment
@zippec

zippec May 10, 2016

@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 commented May 10, 2016

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

@quinisext

This comment has been minimized.

Show comment
Hide comment
@quinisext

quinisext May 10, 2016

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

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

@VSadov

This comment has been minimized.

Show comment
Hide comment
@VSadov

VSadov May 10, 2016

Member

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 {..}
Member

VSadov commented May 10, 2016

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 {..}
@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour May 10, 2016

@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

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.

@quinisext

This comment has been minimized.

Show comment
Hide comment
@quinisext

quinisext May 10, 2016

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

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

@VSadov

This comment has been minimized.

Show comment
Hide comment
@VSadov

VSadov May 10, 2016

Member

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.

Member

VSadov commented May 10, 2016

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.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour May 10, 2016

@VSadov

Unspeakable would be perfectly fine as long as the name is determinstic based on the extended type.

🍝

Extended Generated Name *
int[] 'System.Int32[]<>Extensions'
List<T> 'System.Collections.Generic.List1<>Extensions'` **
List<int> 'System.Collections.Generic.List1<System.Int32>Extensions'`

* 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?

@VSadov

Unspeakable would be perfectly fine as long as the name is determinstic based on the extended type.

🍝

Extended Generated Name *
int[] 'System.Int32[]<>Extensions'
List<T> 'System.Collections.Generic.List1<>Extensions'` **
List<int> 'System.Collections.Generic.List1<System.Int32>Extensions'`

* 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?

@svick

This comment has been minimized.

Show comment
Hide comment
@svick

svick May 10, 2016

Contributor

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

Contributor

svick commented May 10, 2016

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

@nguerrera

This comment has been minimized.

Show comment
Hide comment
@nguerrera

nguerrera May 10, 2016

Member

@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, ..."?

Member

nguerrera commented May 10, 2016

@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, ..."?

@nguerrera

This comment has been minimized.

Show comment
Hide comment
@nguerrera

nguerrera May 10, 2016

Member

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

Member

nguerrera commented May 10, 2016

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

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour May 11, 2016

@nguerrera That's a good argument for requiring the developer to explicitly name the extension class.

@nguerrera That's a good argument for requiring the developer to explicitly name the extension class.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz May 11, 2016

Contributor

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

Contributor

alrz commented May 11, 2016

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

@omariom

This comment has been minimized.

Show comment
Hide comment
@omariom

omariom May 11, 2016

Would be nice if extension members took structs by ref, not by copy.

omariom commented May 11, 2016

Would be nice if extension members took structs by ref, not by copy.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour May 11, 2016

@omariom

#165 seeks to allow that for C# extension methods in general. As mentioned there this is already a supported feature in VB.NET.

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?

@omariom

#165 seeks to allow that for C# extension methods in general. As mentioned there this is already a supported feature in VB.NET.

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?

@VSadov

This comment has been minimized.

Show comment
Hide comment
@VSadov

VSadov May 12, 2016

Member

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.

Member

VSadov commented May 12, 2016

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.

@NoneGiven

This comment has been minimized.

Show comment
Hide comment
@NoneGiven

NoneGiven Jul 27, 2016

The OP is missing an example of instance properties (non-auto-implemented) and never specifies that they are allowed. From the current PRs for this feature, I gather that they are. This could be made a bit clearer for people who are using this proposal to keep informed about the shape of the feature.

NoneGiven commented Jul 27, 2016

The OP is missing an example of instance properties (non-auto-implemented) and never specifies that they are allowed. From the current PRs for this feature, I gather that they are. This could be made a bit clearer for people who are using this proposal to keep informed about the shape of the feature.

@andrew-skybound

This comment has been minimized.

Show comment
Hide comment
@andrew-skybound

andrew-skybound Jul 27, 2016

@grwGeo

Type Extensibility and Extension Members need to be separate issues. The Extension Members feature should enable Type Extensibility, not be the Type Extensibility feature itself.

Furthermore I suspect that getting support for type extensibility baked into the runtime is pretty unlikely, considering the DLR already does this without any special runtime support. If they couldn't make the case for it then, or deemed it unnecessary, chances of it appearing to support a C# language feature are pretty close to nil.

Fortunately, with a robust Extension Members language feature in place, it will be pretty straightforward to roll your own type extensibility mechanism.

@grwGeo

Type Extensibility and Extension Members need to be separate issues. The Extension Members feature should enable Type Extensibility, not be the Type Extensibility feature itself.

Furthermore I suspect that getting support for type extensibility baked into the runtime is pretty unlikely, considering the DLR already does this without any special runtime support. If they couldn't make the case for it then, or deemed it unnecessary, chances of it appearing to support a C# language feature are pretty close to nil.

Fortunately, with a robust Extension Members language feature in place, it will be pretty straightforward to roll your own type extensibility mechanism.

@xen2 xen2 referenced this issue in SiliconStudio/xenko Dec 12, 2016

Closed

VirtualButtonGroup constructor #488

@Thaina Thaina referenced this issue in dotnet/csharplang Feb 21, 2017

Open

Champion "default interface methods" #52

3 of 5 tasks complete

@MadsTorgersen MadsTorgersen referenced this issue in dotnet/csharplang Feb 22, 2017

Open

Exploration: Shapes and Extensions #164

@cloudRoutine cloudRoutine referenced this issue in fsharp/fslang-suggestions Feb 24, 2017

Open

Support type classes or implicits #243

@gafter gafter referenced this issue in dotnet/csharplang Feb 26, 2017

Open

Champion "Extension function members" #192

0 of 5 tasks complete

@zspitz zspitz referenced this issue in dotnet/vblang Mar 10, 2017

Open

[Proposal] Extension method parameter modifer. eg Me #42

1 of 6 tasks complete
@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Mar 12, 2017

Member

This feature request is now tracked at dotnet/csharplang#192

Member

gafter commented Mar 12, 2017

This feature request is now tracked at dotnet/csharplang#192

@gafter gafter closed this Mar 12, 2017

@aarondandy aarondandy referenced this issue in dotnet/csharplang Mar 21, 2017

Closed

A Tour of Default Interface Methods for C# ("traits") #288

0 of 2 tasks complete

@lukaskabrt lukaskabrt referenced this issue in OrchardCMS/OrchardCore May 5, 2017

Merged

View/string localization #732

@Vlad-Stryapko Vlad-Stryapko referenced this issue in dotnet/csharplang Jun 11, 2017

Closed

Numerical Operations on Arrays #671

@MelbourneDeveloper

This comment has been minimized.

Show comment
Hide comment
@MelbourneDeveloper

MelbourneDeveloper Nov 3, 2017

Yeah sharing code in XF is a real pain because this feature does not exist. All the code in WPF, Silverlight, and UWP uses "DataContext", but the same thing in Xamarin Forms is inexplicably "BindingContext". If I could put an extension property on all BindableObjects, immediately, code sharing would be a lot easier.

Yeah sharing code in XF is a real pain because this feature does not exist. All the code in WPF, Silverlight, and UWP uses "DataContext", but the same thing in Xamarin Forms is inexplicably "BindingContext". If I could put an extension property on all BindableObjects, immediately, code sharing would be a lot easier.

@AustinBryan

This comment has been minimized.

Show comment
Hide comment
@AustinBryan

AustinBryan May 18, 2018

@quinisext

In short, extension class is not a descendant of the extended class, so it shouldn't look like one

Why though? : as been used to show things other than inheritance in C# before.

Edit:
Firstly, there's MyClass : IMyInterface. MyClass isn't inheriting IMyInterface, it's implementing it. In Java, they use extends for classes and implements for interfaces. That's Another reason why I don't want an extends , or extensionof keyword, is that it might seem like it's inheritance for Java people.

Having public extension class : MyClass shows that this is a special type of class whereas public class extensionof MyClass might be confused with inheritence and just in general feels more like Java and less like C#.

where T : class, where T : new(), where T : struct. All of those are not things that are even remotely possible to inherit from, not in the way sealed classes can't be inherited, either, but we know what it means, and it's cleaner.

And I actually like the naming of the classes better than just using the type, it feels more like what's really going on: I'm making a new class that the compiler will treat as part of the original.

AustinBryan commented May 18, 2018

@quinisext

In short, extension class is not a descendant of the extended class, so it shouldn't look like one

Why though? : as been used to show things other than inheritance in C# before.

Edit:
Firstly, there's MyClass : IMyInterface. MyClass isn't inheriting IMyInterface, it's implementing it. In Java, they use extends for classes and implements for interfaces. That's Another reason why I don't want an extends , or extensionof keyword, is that it might seem like it's inheritance for Java people.

Having public extension class : MyClass shows that this is a special type of class whereas public class extensionof MyClass might be confused with inheritence and just in general feels more like Java and less like C#.

where T : class, where T : new(), where T : struct. All of those are not things that are even remotely possible to inherit from, not in the way sealed classes can't be inherited, either, but we know what it means, and it's cleaner.

And I actually like the naming of the classes better than just using the type, it feels more like what's really going on: I'm making a new class that the compiler will treat as part of the original.

@AustinBryan

This comment has been minimized.

Show comment
Hide comment
@AustinBryan

AustinBryan May 18, 2018

Another idea would be to add a modifier for classes that is basically the extension-everything counterpart to sealed, in that it prevents people from extending your class.

Something like public unextendable class Person would make Person not able to be extended.

Another idea would be to add a modifier for classes that is basically the extension-everything counterpart to sealed, in that it prevents people from extending your class.

Something like public unextendable class Person would make Person not able to be extended.

@eyalsk

This comment has been minimized.

Show comment
Hide comment
@eyalsk

eyalsk May 18, 2018

@AustinBryan

Another idea would be to add a modifier for classes that is basically the extension-everything counterpart to sealed, in that it prevents people from extending your class.

Something like public unextendable class Person would make Person not able to be extended.

Why would you want to do that? this sounds like a bad idea to me because you shouldn't care whether people extending your types, it's their choice, not yours as they don't change any behaviour, they add new on top of existing one and atm the experience is unpleasant, I mean in order to "extend" types today, depends on the situation, you have to copy/paste the code, use wrappers or static classes and then at the consumer side you have to relate to some other type as opposed to the original type that was "extended" so why would you add a feature that tries to make the experience better and address this problem only to reintroduce the exact same problem?

It makes sense to seal a class or individual methods because people don't want others to modify the behaviour but it doesn't make sense to prevent others from extending it unless the person is a "control freak". 😄

eyalsk commented May 18, 2018

@AustinBryan

Another idea would be to add a modifier for classes that is basically the extension-everything counterpart to sealed, in that it prevents people from extending your class.

Something like public unextendable class Person would make Person not able to be extended.

Why would you want to do that? this sounds like a bad idea to me because you shouldn't care whether people extending your types, it's their choice, not yours as they don't change any behaviour, they add new on top of existing one and atm the experience is unpleasant, I mean in order to "extend" types today, depends on the situation, you have to copy/paste the code, use wrappers or static classes and then at the consumer side you have to relate to some other type as opposed to the original type that was "extended" so why would you add a feature that tries to make the experience better and address this problem only to reintroduce the exact same problem?

It makes sense to seal a class or individual methods because people don't want others to modify the behaviour but it doesn't make sense to prevent others from extending it unless the person is a "control freak". 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment