dotnet / csharplang

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

Champion "Extension function members" #192

Open gafter opened 7 years ago

gafter commented 7 years ago

See also https://github.com/dotnet/roslyn/issues/11159

Thaina commented 7 years ago

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

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

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

orthoxerox commented 7 years ago

@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 commented 7 years ago

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 commented 7 years ago

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

ghost commented 7 years ago

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

hacklex commented 7 years ago

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 commented 7 years ago

@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 commented 7 years ago

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

UweKeim commented 7 years ago

This is the link to the video, BTW

Joe4evr commented 7 years ago

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

IanKemp commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

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

jnm2 commented 6 years ago

https://github.com/dotnet/roslyn/issues/11159#issue-153598385:

Limitations:

  • Events not permitted (at first)
leo60228 commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

@Joe4evr

HaloFour commented 6 years ago

@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 commented 6 years ago

No, didn't see that proposal. Sorry!

On Tue, Mar 13, 2018, 6:52 AM HaloFour notifications@github.com wrote:

@leo60228 https://github.com/leo60228

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

@Joe4evr https://github.com/joe4evr @Thaina https://github.com/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.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dotnet/csharplang/issues/192#issuecomment-372624840, or mute the thread https://github.com/notifications/unsubscribe-auth/AH996e-Fq-juLIX00Y2Xu-T31qxJfrHtks5td6TsgaJpZM4MMgFz .

TheFanatr commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

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.

Gorthog commented 6 years ago

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

Exactly.

LokiMidgard commented 5 years ago

This would be very helpful writing generic wrapper classes that will override the compare Operator only if the generic type argument supports it.

Do I see it correctly that I would need an separate extension class for every concrete generic type. E.g. My Wraper should support + when the generic type support it. So I will need one class and not only one Method for each type int, double, float, long .....

public class E<T>{ /* ...*/ }

public extension class EExtesnions<T>  : E<T>  where T :IComparable<T>
{
   //...
   public static  E<bool> operator <(E<T> left, E<T> right) => // implementation;

   // this is not allowed vvvv
   public static  E<int> operator +(E<int> left, E<int> right) => // implementation;
   public static  E<double> operator +(E<double> left, E<double> right) => // implementation;
}

public extension class EIntExtension  : E<int> 
{
   //...
   public static  E<int> operator +(E<int> left, E<int> right) => // implementation;
}
public extension class EIntExtension  : E<double> 
{
   //...
   public static  E<double> operator +(E<double> left, E<double> right) => // implementation;
}
AnorZaken commented 5 years ago

So how about keeping it familiar, but still different enough to not cause confusion - instead of the normal single colon : for inheritance, this could use double colons :: public static class MyExtensions :: ExtendedClass Or has anyone suggested this already?

PathogenDavid commented 5 years ago

While it isn't commonly used (and wouldn't make sense in that context), it's worth noting that :: is already in use for namespace alias qualifiers.

Personally, I'd rather see the introduction of the extension keyword in place of static for extension types. It puts more emphasis on the fact that the type plays a special role compared to others.

matthew25187 commented 5 years ago

Rather than having a special type of extensions class, why not modify the existing syntax for extension methods? For example,

public static class DayOfWeekExtensions
{
  // Methods
  public static bool IsWeekend() extends DayOfWeek
  {
     return this == DayOfWeek.Saturday || this == DayOfWeek.Sunday;
  }

  // Properties
  public static bool IsWeekend extends DayOfWeek
  {
     get => this == DayOfWeek.Saturday || this == DayOfWeek.Sunday;
  }
}

The extends keyword defines the context of the this keyword within the body of the extension. This allows for a single extensions class to extend the capabilities of multiple types, as do existing extension classes that currently implement extension methods. Extension methods could also overload methods from the extended type, so long as the method signature is unique. If the extended type is later modified to add an overload that is already defined as an extension, the extension overload would have to be flagged as a compilation error.

You could also extend interfaces, where the this keyword refers to an instance of the interface. For example,

public static class EnumerableExtensions
{
    public static int CountNulls<T>() extends IEnumerable<T> where T : class
    {
        return this.Count(item => item == null);
    }
}

When using generics, as in this example, the generic type arguments would be limited to those employed by the extended type so the compiler would know their context at the point the extension was called.

This strategy could also be applied to other constructs such as indexers.

// Contrived example!
namespace ExtensionsExample
{
    public struct FirstName
    {
        private string _name;

        public FirstName(string name)
        {
            _name = name;
        }
    }

    public struct LastName : IEquatable<LastName>
    {
        private string _name;

        public LastName(string name)
        {
            _name = name;
        }

        public bool Equals(LastName other)
        {
            return other._name == _name;
        }
    }

    public class PersonName
    {
        public FirstName FirstName {get; set;}
        public LastName LastName {get; set;}
    }

    public class PersonNameCollection : CollectionBase
    {
        // Collection stuff.
    }

    public class Person
    {
        public PersonNameCollection Names {get;}
    }

    public static class PersonExtensions
    {
        public static PersonName this[LastName index] extends PersonNameCollection
        {
            get => this.Cast<PersonName>().SingleOrDefault(item => item.LastName.Equals(index));
        }
    }
}
matthew25187 commented 5 years ago

How would a class being sealed affect the ability of extensions to add capabilities to it? Would the sealed keyword prohibit the definition of extensions on the class?

brunoais commented 5 years ago

sealed only affects the class. An extension is, in its essence, syntactic sugar for a wrapper around the original class and to make as if the original class had those methods. It never changes that class itself.

AnorZaken commented 5 years ago

When using generics, as in this example, the generic type arguments would be limited to those employed by the extended type so the compiler would know their context at the point the extension was called.

I would never accept the loss of defining my own generic types in an extension. That's a serious regression of functionality ...or I'm misunderstanding you.

willmotil commented 5 years ago

Since you guys are discussing this in relation to stringbuilder as well. You may like to see a practical StringBuilder wrapper class that i made and keep updating that i use in practice gif at the bottom. In order to solve problems with the regular StringBuilder, primarily with 2 things that it falls short in both relate to Garbage Collections depending on the context Massive amounts for even simple things.

1) it's Insert method. 2) c# conversion of numeric variables into strings to the StringBuilder in a dynamic context.

Both of these can generate a huge amount of garbage in a realtime situation with massive massive amounts of collections.

Operator Overloading is the least of its problems however it is a garbage collection problem if used.

(I know you guys can do it better then me.)

Since you are all pro's here i will not remake a new project just to remove the MonoGame specific methods which will error for straight c# use. They can just be commented out there are only a couple small append methods.

The code to illustrate and the output below that in a gif.

        MgStringBuilder msg = new MgStringBuilder("Hello World ");
        int counter = 0;
        protected override void QuickDrawSpriteBatch(GameTime gameTime)
        {
            msg.Clear();
            counter++;
            //msg = " *operator* "; // fine 
            //msg += " Hello " + "World "; // works but creates garbage due to operator overloading limitations.
            msg.Append(" *Append* ");
            msg.Append("Hello ").Append("World ").Append(counter); // i can make this work with no garbage.

            // print out
            //Console.WriteLine(msg);

            // in my case i draw
            spriteBatch.DrawString(Gu.currentFont, msg, new Vector2(300, 10), Color.Wheat);
        }

My Wrapper.

https://github.com/willmotil/MonoGameUtilityClasses/blob/master/MgStringBuilder.cs

gcstringbuilder01

This is actually running at over 2000 fps my gif program slowed it down when i was recording though.

tullo-x86 commented 5 years ago

@willmotil, I think you might have posted your comment on the wrong issue...

ddobrev commented 5 years ago

Is there any actual work ongoing? When is this going to be available?

Trojaner commented 5 years ago

@ddobrev it is in the X.0 candidate milestone, which means it's on the candidate list for a future version. I assume no work on this is currently done as C# 8 is still not finished.

michael-hawker commented 5 years ago

Yeah, I was sad to see previews coming out that didn't have this feature. Been waiting for this one!

lobster2012-user commented 4 years ago

Many of fsharp's features have been ported to csharp. Unfortunately, this did not affect custom operators, I hope this will be fixed. custom operator |> csharp

ddobrev commented 4 years ago

C# 8 seems finished and this isn't in. When is it going to?

CyrusNajmabadi commented 4 years ago

@ddobrev This is no release that this feature is scheduled for.

CyrusNajmabadi commented 4 years ago

@ddobrev Your comment isn't helpful or constructive. Please try to stay on topic. I understand you very much want this feature. However, this is one of thousands of features that people would like and we're prioritizing based on lots of pieces of data to determine which ones we should ship.

Importantly, many features we do are not at all sugar (though many are as well). Much of the last few releases has been driving work that is enormously important for scalability and performance (especially for games, server environments, and mobile devices).

Hopefully in the future you'll find our picks are more what you want. But that won't always be the case. Have a good day :)

munael commented 4 years ago

Any definition doc to read for this proposal? It mentions "Extension Everything" under the roslyn repo. But that's

  1. Under a different repo, and is closed.
  2. Uses different language from this issue.

What are "Extension function members"? Is there a post missing or is missing a log from an older issue?

CyrusNajmabadi commented 4 years ago

@narfanar there is no proposal yet:

image

ChayimFriedman2 commented 4 years ago

Personally, I discourage field extensions. This is against the philosophy of adding a helper to the class. If you need to store data, that's not a helper - that data structure. And data structure should be implemented by inheritance, probably.

Also that will face many technical challenges, which will cause either poor performance or introducing changes in the CLR (or both) because the only ways (do you have others?) to implement this feature is a) implement a dictionary for the field (performing poorly) or b) somehow extends the CLR so objects can have additional fields. But properties (and indexers, etc.) are welcome.

Interfaces are also good (i.e. implementing interface through extensions) since interfaces indicates an ability of the type. For instance, be serialized, format itself, etc..

NetMage commented 4 years ago

@CyrusNajmabadi Is Mads no longer working?

Carsillas commented 4 years ago

Hard to determine the current proposal syntax from this thread and I'm not certain where to find it elsewhere. I've seen some mentions of proposals where they keyword static isn't present. I'm fairly uneducated on the backend of how this all works but currently it looks like the IL extension methods are static, if this would go unchanged the [MethodImpl(MethodImplOptions.Synchronized)] attribute would be very unintuitive on extension methods/properties. Seems the attribute currently would lock "statically" not for each class instance (makes sense given the static method) but if the static keyword is removed that could be pretty confusing.

quinmars commented 4 years ago

The use of MethodImplOptions.Synchronized is discouraged anyway:

Locking on the instance or on the type, as with the Synchronized flag, is not recommended for public types, because code other than your own can take locks on public types and instances. This might cause deadlocks or other synchronization problems.

michael-hawker commented 4 years ago

I'm not sure if it'd be part of this proposal or a new feature request. But I just encountered a scenario where I wanted to have an extension that was using a protected method, but I'd only be calling the extension method from within the scope of the parent class which has access to the protected method.

It'd be great to be able to flag this type of scenario so the compiler would let me access the protected sub-class methods as long as I'm only using the extension method from a place where I'd have access to those compared to just anywhere.

E.g. In XAML, when extending and writing a custom Control, we inherit from the Control class which has a protected GetTemplateChild method. However this is generalized and not strongly typed. We wanted to create a typed version which would encapsulate the cast and check for null if we thought it was a required component.

We do this as a private method currently, but it is a general function applicable to anyone inheriting from Control and would have been great to generalize and re-use code more effectively with an extension method.

PhilParisot commented 3 years ago

I just encountered a scenario where I'm using an API and I'm unable to modify the base class but must use its derived classes (because of added logic existing only in the different derived classes).

I looked into extension methods and it almost gave me what I wanted but I still needed to add custom properties...

Then I looked into adding an interface, and again, it almost did what I needed it to do but interfaces can't tell what class/type it'll be using and therefore can't access said class's public members which the method I was writing again needed.

The method I'd like to use has the exact same implementation on all derived classes, but now I am forced to re-implement it anyways, and it's just not as clean, not to mention time consuming.

Add in the benefit that having the possibility to use class extensions you could write everything you need for your personal use case once, have it work for you, then easily create a pull request with all the same code (or close) and let the maintainer decide whether to merge or not in the main repo. In any case your version still works fine for you and it might even get merged and contribute to the ecosystem at large.

It's a win-win, two birds one stone.

lukasf commented 3 years ago

Allowing extension methods for implicit casts would be very helpful to simplify using the new JsonNode APIs in .NET 6:

https://github.com/dotnet/runtime/issues/53195#issuecomment-856223460

I don't believe that implicit casts will ever be added to the actual JsonObject classes. But if we could define them in an extension method in a sub-namespace, users could easily opt-in to implicit conversions by including the sub-namespace.