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

Champion "Extension function members" #192

Open
gafter opened this Issue Feb 26, 2017 · 25 comments

Comments

@gafter
Member

gafter commented Feb 26, 2017

  • Proposal added
  • Discussed in LDM
  • Decision in LDM
  • Finalized (done, rejected, inactive)
  • Spec'ed

See also dotnet/roslyn#11159

@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Feb 27, 2017

Almost agree and totally support with a bit of arguments and concerns

  • Name of this feature make it not sure if this will be able to extend field or not, I would disagree to extend fields

  • Extension struct should always pass by reference. Same behaviour as normal implementation of struct. This solve the problem of passing struct by ref for extension method altogether

  • How can we find duplicate operator? When it cause function conflict error it will be hard to find, unlike named member that could go to reference

  • Would this syntax will be able to extend enum ?

  • Is this include extension to implement interface ?

  • We don't need new keyword extension if we could permit static class to extend anything. Reusing keyword and it already intuitive to have static class hold extension method

/// static class cannot extend anything before so it not breaking any previous code
public static class Ext : MyStruct
{
}

Thaina commented Feb 27, 2017

Almost agree and totally support with a bit of arguments and concerns

  • Name of this feature make it not sure if this will be able to extend field or not, I would disagree to extend fields

  • Extension struct should always pass by reference. Same behaviour as normal implementation of struct. This solve the problem of passing struct by ref for extension method altogether

  • How can we find duplicate operator? When it cause function conflict error it will be hard to find, unlike named member that could go to reference

  • Would this syntax will be able to extend enum ?

  • Is this include extension to implement interface ?

  • We don't need new keyword extension if we could permit static class to extend anything. Reusing keyword and it already intuitive to have static class hold extension method

/// static class cannot extend anything before so it not breaking any previous code
public static class Ext : MyStruct
{
}
@Bartmax

This comment has been minimized.

Show comment
Hide comment
@Bartmax

Bartmax Jun 7, 2017

I think I'm late to the party and forgive my ignorance, but what about just supporting partial keyword for non-partial classes ?

Bartmax commented Jun 7, 2017

I think I'm late to the party and forgive my ignorance, but what about just supporting partial keyword for non-partial classes ?

@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Jun 7, 2017

@Bartmax partial is for classes in the same assembly. It simply combines several pieces of the same type during compilation. Extensions are for extending arbitrary classes, including those from external compiled assemblies.

orthoxerox commented Jun 7, 2017

@Bartmax partial is for classes in the same assembly. It simply combines several pieces of the same type during compilation. Extensions are for extending arbitrary classes, including those from external compiled assemblies.

@Bartmax

This comment has been minimized.

Show comment
Hide comment
@Bartmax

Bartmax Jun 7, 2017

i know that, but that doesn't mean it can be "reworked" to allow classes from other assemblies hence creating extensions for arbitrary classes.

Bartmax commented Jun 7, 2017

i know that, but that doesn't mean it can be "reworked" to allow classes from other assemblies hence creating extensions for arbitrary classes.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Jul 6, 2017

Contributor

I'd suggest the following syntax for extension declarations,

internal extension [class] StringExtensions for String { }
internal extension [class] GenericExtensions<T> for T where T : class { }

I believe the name should be optional for convenience (e.g. private nested extension declarations don't really need to be named):

private extension [for] String { }
private extension<T> [for] T where T : class { }

Similarly, instead of base list, we could use implement to plug interface/shape/traits to types,

implement TraitT for String { }
implement<T> FooT for T where T : BarT { }

This has various advantages like segregating impls so that they do not show up on all instances. Explicit implementations could have the same effect in extension declarations, but I think it's good to separate these concerns (auxiliary methods vs trait implementations).

Contributor

alrz commented Jul 6, 2017

I'd suggest the following syntax for extension declarations,

internal extension [class] StringExtensions for String { }
internal extension [class] GenericExtensions<T> for T where T : class { }

I believe the name should be optional for convenience (e.g. private nested extension declarations don't really need to be named):

private extension [for] String { }
private extension<T> [for] T where T : class { }

Similarly, instead of base list, we could use implement to plug interface/shape/traits to types,

implement TraitT for String { }
implement<T> FooT for T where T : BarT { }

This has various advantages like segregating impls so that they do not show up on all instances. Explicit implementations could have the same effect in extension declarations, but I think it's good to separate these concerns (auxiliary methods vs trait implementations).

@kreig

This comment has been minimized.

Show comment
Hide comment
@kreig

kreig Jul 18, 2017

Please consider this feature for the nearest releases. It would make life so much easier.

kreig commented Jul 18, 2017

Please consider this feature for the nearest releases. It would make life so much easier.

@hacklex

This comment has been minimized.

Show comment
Hide comment
@hacklex

hacklex Aug 25, 2017

I think it would be good to have the ability to write extensions for multiple classes in a single context. For example, if you need to cache reflection objects (especially when you're emitting something), sharing said cache would probably be a good idea, and creating an additional static class for that sole purpose would feel somewhat awkward.

Also, I think many would benefit from extensions existing in a non-global context (consider private static void ExtMethod(this SomeClass x) being inside a non-static class).

hacklex commented Aug 25, 2017

I think it would be good to have the ability to write extensions for multiple classes in a single context. For example, if you need to cache reflection objects (especially when you're emitting something), sharing said cache would probably be a good idea, and creating an additional static class for that sole purpose would feel somewhat awkward.

Also, I think many would benefit from extensions existing in a non-global context (consider private static void ExtMethod(this SomeClass x) being inside a non-static class).

@paulomorgado

This comment has been minimized.

Show comment
Hide comment
@paulomorgado

paulomorgado Aug 26, 2017

@Bartmax, there is no such thing as partial classes. There are partial class definitions and it's a source code feature not an assembly feature (@orthoxerox).

paulomorgado commented Aug 26, 2017

@Bartmax, there is no such thing as partial classes. There are partial class definitions and it's a source code feature not an assembly feature (@orthoxerox).

@gmengano

This comment has been minimized.

Show comment
Hide comment
@gmengano

gmengano Oct 25, 2017

X.0 or 8.0?
(See A preview of c# 8 with Mads Torgersen)

gmengano commented Oct 25, 2017

X.0 or 8.0?
(See A preview of c# 8 with Mads Torgersen)

@UweKeim

This comment has been minimized.

Show comment
Hide comment

UweKeim commented Oct 25, 2017

@Joe4evr

This comment has been minimized.

Show comment
Hide comment
@Joe4evr

Joe4evr Oct 25, 2017

Well, it's 100% in the X.0 Candidate milestone. And you can click it if you don't know what that means.

Joe4evr commented Oct 25, 2017

Well, it's 100% in the X.0 Candidate milestone. And you can click it if you don't know what that means.

@IanKemp

This comment has been minimized.

Show comment
Hide comment
@IanKemp

IanKemp Nov 8, 2017

@Joe4evr That milestone has a release date of January 1 2100 - I think most of us would like to see this feature in a version of C# that's released before we're all dead...

IanKemp commented Nov 8, 2017

@Joe4evr That milestone has a release date of January 1 2100 - I think most of us would like to see this feature in a version of C# that's released before we're all dead...

@JVimes

This comment has been minimized.

Show comment
Hide comment
@JVimes

JVimes Nov 29, 2017

@paulomorgado and @orthoxerox, I'm sure @Bartmax is not suggesting using partial class definitions. He's suggesting using a similar syntax to theirs, but for extension members. It's a great idea:

public extension class ClassName
{
    // Extension members of all types go here
    // Don't need to pollute parameter lists with "this ClassName foo"
}

JVimes commented Nov 29, 2017

@paulomorgado and @orthoxerox, I'm sure @Bartmax is not suggesting using partial class definitions. He's suggesting using a similar syntax to theirs, but for extension members. It's a great idea:

public extension class ClassName
{
    // Extension members of all types go here
    // Don't need to pollute parameter lists with "this ClassName foo"
}
@ivi-hamiti

This comment has been minimized.

Show comment
Hide comment
@ivi-hamiti

ivi-hamiti Dec 1, 2017

@JVimes I totally agree with your proposed syntax so not to introduce anymore new keyword like the example with the for keyword. But what about the cases you are implementing extension methods for types that implement a certain interfaces? Event the naming would be misleading. Like we are doing and extension class for IEnumerable and you have to do something like the following:

public extension class IEnumerable<T>
{
    // Extension members of all types go here
    // Don't need to pollute parameter lists with "this ClassName foo"
}

Which breaks the naming conventions for C#. Based on your approach i would go with a syntax like this

public extension class ExtensionName : ClassName 
{
    // Extension members of all types go here
    // Don't need to pollute parameter lists with "this ClassName foo"
}

So it should support the same syntax as class inheritance, generics, etc. The only thing with this approach is that it may be misleading to that user, thinking he should implement the interface or abstract class (as it looks like you are extending/implementing some type.

ivi-hamiti commented Dec 1, 2017

@JVimes I totally agree with your proposed syntax so not to introduce anymore new keyword like the example with the for keyword. But what about the cases you are implementing extension methods for types that implement a certain interfaces? Event the naming would be misleading. Like we are doing and extension class for IEnumerable and you have to do something like the following:

public extension class IEnumerable<T>
{
    // Extension members of all types go here
    // Don't need to pollute parameter lists with "this ClassName foo"
}

Which breaks the naming conventions for C#. Based on your approach i would go with a syntax like this

public extension class ExtensionName : ClassName 
{
    // Extension members of all types go here
    // Don't need to pollute parameter lists with "this ClassName foo"
}

So it should support the same syntax as class inheritance, generics, etc. The only thing with this approach is that it may be misleading to that user, thinking he should implement the interface or abstract class (as it looks like you are extending/implementing some type.

@michael-hawker

This comment has been minimized.

Show comment
Hide comment
@michael-hawker

michael-hawker Dec 17, 2017

So, would this proposal cover being able to add events as an extension to an existing class as well?

michael-hawker commented Dec 17, 2017

So, would this proposal cover being able to add events as an extension to an existing class as well?

@jnm2

This comment has been minimized.

Show comment
Hide comment
@jnm2

jnm2 Dec 18, 2017

Contributor

dotnet/roslyn#11159 (comment):

Limitations:

  • Events not permitted (at first)
Contributor

jnm2 commented Dec 18, 2017

dotnet/roslyn#11159 (comment):

Limitations:

  • Events not permitted (at first)
@leo60228

This comment has been minimized.

Show comment
Hide comment
@leo60228

leo60228 Mar 11, 2018

I assume this would support interfaces. This would be really neat, since you could make a mixin like this (with the syntax specified by ivi-hamiti):

interface IMyMixin {}

public extension class MyMixinImpl : IMyMixin {
    // mixin stuff here
}

leo60228 commented Mar 11, 2018

I assume this would support interfaces. This would be really neat, since you could make a mixin like this (with the syntax specified by ivi-hamiti):

interface IMyMixin {}

public extension class MyMixinImpl : IMyMixin {
    // mixin stuff here
}
@Joe4evr

This comment has been minimized.

Show comment
Hide comment
@Joe4evr

Joe4evr Mar 13, 2018

@leo60228 Is there any reason you'd need this over Default Interface Methods #52 to create mixins?

Interfaces will probably be supported just the same, but this proposal is more for the cases where you don't own the type in question.

Joe4evr commented Mar 13, 2018

@leo60228 Is there any reason you'd need this over Default Interface Methods #52 to create mixins?

Interfaces will probably be supported just the same, but this proposal is more for the cases where you don't own the type in question.

@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Mar 13, 2018

@Joe4evr

  • It keep interface being just interface
  • It can extend across namespace and assembly
  • It more flexible
  • It let we have the same expectation to current extension method. Current extension method support interface and generic constraint. All object implement extended interface can also call all of its extension method. If we could extend all member then this approach is more familiar

Thaina commented Mar 13, 2018

@Joe4evr

  • It keep interface being just interface
  • It can extend across namespace and assembly
  • It more flexible
  • It let we have the same expectation to current extension method. Current extension method support interface and generic constraint. All object implement extended interface can also call all of its extension method. If we could extend all member then this approach is more familiar
@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 13, 2018

Contributor

@leo60228

It would support interfaces the same way that extension methods do now.

@Joe4evr @Thaina

The use of extensions here does not replace default method implementations anymore than extension methods currently do. They are external, cannot be virtual/specialized and the feature does not enable safe evolution of existing contracts.

Contributor

HaloFour commented Mar 13, 2018

@leo60228

It would support interfaces the same way that extension methods do now.

@Joe4evr @Thaina

The use of extensions here does not replace default method implementations anymore than extension methods currently do. They are external, cannot be virtual/specialized and the feature does not enable safe evolution of existing contracts.

@leo60228

This comment has been minimized.

Show comment
Hide comment
@leo60228

leo60228 Mar 13, 2018

leo60228 commented Mar 13, 2018

@TheFanatr

This comment has been minimized.

Show comment
Hide comment
@TheFanatr

TheFanatr Jun 6, 2018

Type Extensions Syntax Proposition

The following is a syntax and language feature proposition for how extension types would work.

Example

This is an example of what the syntax would look like, assuming all of the types are defined in the same namespace.

Dependency

public class SampleDependedType
{
    public string SampleStringVariableA { get; protected set; } = String.Empty;

    public string SampleStringVariableB { get; set; } = String.Empty;

    public int SampleIntegerVariableA { get; set; } = 0;
}

Hypothetical Extending Class

public extending class SampleExtendingType : SampleDependedType
{
    public string ToString() => $"\{ {SampleStringVariableA}, {SampleStringVariableB}, {SampleIntegerVariableA} \}";

    public void Reset() => (SampleStringVariableA, SampleStringVariableB, SampleIntegerVariableA) = (String.Empty, String.Empty, 0);

    public string SampleStringVariableA { get; set; }
}

Usage of Hypothetical Extending Class

using System;

using static SampleExtendingType;

class Program
{
    static void Main()
    {
        var sampleDependedType = new SampleDependedType { SampleStringVariableA = "Hello" };
        var sampleExtendingType = new SampleExtendingType { SampleStringVariableB = "What is up" };

        Console.WriteLine(sampleDependedType.ToString()); // Result: { Hello, , 0 }
        Console.WriteLine(sampleDependedType.SampleIntegerVariableA == sampleExtendingType.SampleIntegerVariableA); // Result: true

        sampleDependedType.Reset();

        sampleExtendingType.SampleIntegerA = 2;

        Console.WriteLine(sampleDependedType.ToString()); // Result: { , , 0 }
        Console.WriteLine(sampleExtendingType.ToString()); // Result: { , What is up, 2 }

        Console.WriteLine(sampleExtendingType is sampleDependedType); // Result: true
    }
}

Usage Explanation

The keyword extending would be placed on the type declaration for any class that is to be an extension of another; deriving from a class with the extending keyword would cause the new type to extend the derived type. The word extending was chosen to reflect the pattern of using statement words as keywords, such as protected and sealed; extending is being used in reference to the phrase "the extending class", in the same way that the word ageing is being used in reference to the phrase "the ageing oak tree".

The extending type is the one that is the extending class that it is derived from, meaning that the type's definition is defining an extension to the type that was derived from. Using the extending type statically will cause it to be merged in functionality with the class it is extending, but will still be usable as a standalone type, as a normal derived class. The declaration of SampleStringVariableA in the extension class defines an implicit encapsulation of the functionality of SampleStringVariableA from the derived-from type and overrides the access modifier for the setter because the extension type, just as any other derived type, has access to protected setters within the type they are derived from and can thus define a differently-accessible delegate that encapsulates the functionality of the original setter delegate.

Additional Features

Generic Extensions

The system should also allow for the declaration of generic extensions that would programmatically be defined as generic classes with the same non-generic members as the derived-from class. Having this ability would be very useful as it would greatly simplify the process of creating generic and non-generic versions of a class; a publicly-inaccessible base class could be created and derived from in both a generic and a non-generic extension of itself, instantly defining two different types with the same non-generic members and with the same name.

Alternatives

The keyword extending could be switched out with extension but then a double noun issue would be introduced, as the syntactical declaration would already specify the "type" of the syntactical structure being defined, such as class or struct, so specifying extension as well creates ambiguity in what the "type" of the structure is, and what the pseudo-adjective is; is it a class extension or an extension class. It would then be rather more appropriate to replace both words with something like class-extension or extension-class; however, in such a case it would probably be simpler and more intuitive if the keyword class were to be removed altogether. Some may point to abstract class to argue that it would be valid, but the word abstract is an adjective, and so class abstract does not makes sense, which is why it works.

Usefulness

This language feature should be worked on more actively because it can be made into a very useful new tool for easily creating types with common members. Since, according to this pseudo-specification, extensions are in themselves types but with direct syntactical access to, from the perspective of a client, the members of the type that they are derived from, this feature would make it extremely easy to define a bunch of types that all have the same base members such as constructors, indexers, and all else defined and/or declared in the derived-from type. Attributes and/or modifiers would also be copied over. In this case, the keyword to create this syntactical structure should just be extension without any other noun such as class or struct.

TheFanatr commented Jun 6, 2018

Type Extensions Syntax Proposition

The following is a syntax and language feature proposition for how extension types would work.

Example

This is an example of what the syntax would look like, assuming all of the types are defined in the same namespace.

Dependency

public class SampleDependedType
{
    public string SampleStringVariableA { get; protected set; } = String.Empty;

    public string SampleStringVariableB { get; set; } = String.Empty;

    public int SampleIntegerVariableA { get; set; } = 0;
}

Hypothetical Extending Class

public extending class SampleExtendingType : SampleDependedType
{
    public string ToString() => $"\{ {SampleStringVariableA}, {SampleStringVariableB}, {SampleIntegerVariableA} \}";

    public void Reset() => (SampleStringVariableA, SampleStringVariableB, SampleIntegerVariableA) = (String.Empty, String.Empty, 0);

    public string SampleStringVariableA { get; set; }
}

Usage of Hypothetical Extending Class

using System;

using static SampleExtendingType;

class Program
{
    static void Main()
    {
        var sampleDependedType = new SampleDependedType { SampleStringVariableA = "Hello" };
        var sampleExtendingType = new SampleExtendingType { SampleStringVariableB = "What is up" };

        Console.WriteLine(sampleDependedType.ToString()); // Result: { Hello, , 0 }
        Console.WriteLine(sampleDependedType.SampleIntegerVariableA == sampleExtendingType.SampleIntegerVariableA); // Result: true

        sampleDependedType.Reset();

        sampleExtendingType.SampleIntegerA = 2;

        Console.WriteLine(sampleDependedType.ToString()); // Result: { , , 0 }
        Console.WriteLine(sampleExtendingType.ToString()); // Result: { , What is up, 2 }

        Console.WriteLine(sampleExtendingType is sampleDependedType); // Result: true
    }
}

Usage Explanation

The keyword extending would be placed on the type declaration for any class that is to be an extension of another; deriving from a class with the extending keyword would cause the new type to extend the derived type. The word extending was chosen to reflect the pattern of using statement words as keywords, such as protected and sealed; extending is being used in reference to the phrase "the extending class", in the same way that the word ageing is being used in reference to the phrase "the ageing oak tree".

The extending type is the one that is the extending class that it is derived from, meaning that the type's definition is defining an extension to the type that was derived from. Using the extending type statically will cause it to be merged in functionality with the class it is extending, but will still be usable as a standalone type, as a normal derived class. The declaration of SampleStringVariableA in the extension class defines an implicit encapsulation of the functionality of SampleStringVariableA from the derived-from type and overrides the access modifier for the setter because the extension type, just as any other derived type, has access to protected setters within the type they are derived from and can thus define a differently-accessible delegate that encapsulates the functionality of the original setter delegate.

Additional Features

Generic Extensions

The system should also allow for the declaration of generic extensions that would programmatically be defined as generic classes with the same non-generic members as the derived-from class. Having this ability would be very useful as it would greatly simplify the process of creating generic and non-generic versions of a class; a publicly-inaccessible base class could be created and derived from in both a generic and a non-generic extension of itself, instantly defining two different types with the same non-generic members and with the same name.

Alternatives

The keyword extending could be switched out with extension but then a double noun issue would be introduced, as the syntactical declaration would already specify the "type" of the syntactical structure being defined, such as class or struct, so specifying extension as well creates ambiguity in what the "type" of the structure is, and what the pseudo-adjective is; is it a class extension or an extension class. It would then be rather more appropriate to replace both words with something like class-extension or extension-class; however, in such a case it would probably be simpler and more intuitive if the keyword class were to be removed altogether. Some may point to abstract class to argue that it would be valid, but the word abstract is an adjective, and so class abstract does not makes sense, which is why it works.

Usefulness

This language feature should be worked on more actively because it can be made into a very useful new tool for easily creating types with common members. Since, according to this pseudo-specification, extensions are in themselves types but with direct syntactical access to, from the perspective of a client, the members of the type that they are derived from, this feature would make it extremely easy to define a bunch of types that all have the same base members such as constructors, indexers, and all else defined and/or declared in the derived-from type. Attributes and/or modifiers would also be copied over. In this case, the keyword to create this syntactical structure should just be extension without any other noun such as class or struct.

@michael-hawker

This comment has been minimized.

Show comment
Hide comment
@michael-hawker

michael-hawker Jun 6, 2018

@TheFanatr protected and sealed are not present-tense words. extended would fit the current pattern of past tense words better.

It'd be also great to see method and operator overload examples in the sample. Also, a call out that while the sample shows the same namespace, that isn't a restriction/limitation of the feature.

michael-hawker commented Jun 6, 2018

@TheFanatr protected and sealed are not present-tense words. extended would fit the current pattern of past tense words better.

It'd be also great to see method and operator overload examples in the sample. Also, a call out that while the sample shows the same namespace, that isn't a restriction/limitation of the feature.

@TheFanatr

This comment has been minimized.

Show comment
Hide comment
@TheFanatr

TheFanatr Jun 6, 2018

protected and sealed are not present-tense words

Yeah, I know. I think it's a side effect of the fact that I wrote it at 2 AM.

extended would fit the current pattern of past tense words better

It may fit the pattern better; however, it doesn't make sense. Somewhere in there I explained it, but basically, the extension is doing the extending of the derived type, so consequently, the type that it is extending is the "extended" type, not the other way around.

TheFanatr commented Jun 6, 2018

protected and sealed are not present-tense words

Yeah, I know. I think it's a side effect of the fact that I wrote it at 2 AM.

extended would fit the current pattern of past tense words better

It may fit the pattern better; however, it doesn't make sense. Somewhere in there I explained it, but basically, the extension is doing the extending of the derived type, so consequently, the type that it is extending is the "extended" type, not the other way around.

@sinapis

This comment has been minimized.

Show comment
Hide comment
@sinapis

sinapis Sep 4, 2018

the keyword to create this syntactical structure should just be extension without any other noun such as class or struct.

Exactly.

sinapis commented Sep 4, 2018

the keyword to create this syntactical structure should just be extension without any other noun such as class or struct.

Exactly.

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