Skip to content
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
5 tasks
gafter opened this issue Feb 26, 2017 · 76 comments
Open
5 tasks

Champion "Extension function members" #192

gafter opened this issue Feb 26, 2017 · 76 comments
Assignees
Milestone

Comments

@gafter
Copy link
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

@gafter gafter added this to the X.0 candidate milestone Feb 26, 2017
@Thaina
Copy link

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
Copy link

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
Copy link

@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
Copy link

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
Copy link
Member

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

@ghost
Copy link

ghost commented Jul 18, 2017

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

@hacklex
Copy link

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
Copy link

@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
Copy link

gmengano commented Oct 25, 2017

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

@UweKeim
Copy link

UweKeim commented Oct 25, 2017

This is the link to the video, BTW

@Joe4evr
Copy link
Contributor

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
Copy link

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
Copy link

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
Copy link

@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
Copy link

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

@jnm2
Copy link
Contributor

jnm2 commented Dec 18, 2017

dotnet/roslyn#11159 (comment):

Limitations:

  • Events not permitted (at first)

@leo60228
Copy link

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
}

@mrahhal
Copy link

mrahhal commented Dec 15, 2021

I wanted to add one simple to understand scenario in higher level code where this would have been a great feature to have on the library level.

We wanted to support SQL row value syntax in Entity Framework Core (dotnet/efcore#26822). Currently, this is the most readable C# syntax the user will have to write to represent row value:

.Where(b => EF.Functions.GreaterThan(new[] { b.Column1, b.Column2 }, new[] { 1, 2 }))

Not so readable.

This proposal would in theory allow providing custom tuple operators and allow the user to write the following instead:

.Where(b => (b.Column1, b.Column2) > (1, 2))

@jl0pd
Copy link

jl0pd commented Dec 18, 2021

Extension properties would greatly help in dotnet/comet, which heavily uses builder pattern to create views.

Current

new ShapeView(new Circle()
		.Fill(LinearGradient)
		.Style(DrawingStyle.Fill)).Frame(200,100),

With extensions

new ShapeView(new Circle { Fill = LinearGradient, Style = DrawingStyle.Fill }) { Frame = (200, 100) },
// Frame have signature: void set_Frame(this ShapeView view, (double, double) size)

Current

new Text("TEST PADDING").Frame(height:30).Margin(top:100),

With extensions

new Text("TEST PADDING") { FrameHeight = 30, MarginTop = 100, },

I find extension property version more readable, especially in first case

@uecasm
Copy link

uecasm commented Mar 24, 2022

Extension static methods would be useful for classes in testing/mocking frameworks and elsewhere that define fluent methods hanging off a static class like It or Is or Arg etc. Specifically, when you'd like to be able to add an extra method for something custom that works the same way, slightly prettier than making a MyIt class or using some fallback that takes a lambda.

@hawkerm
Copy link

hawkerm commented Jun 3, 2022

@uecasm was just coming to call out the same thing. Being able to write extensions off of Assert for MS Test for example would be useful for folks to write extensions that flow naturally into an existing test framework.

@NetMage
Copy link

NetMage commented Jun 8, 2022

Is there anywhere to see if any progress is being made on this issue?

@theunrepentantgeek
Copy link

Yup. Right here.

@TahirAhmadov
Copy link

Doesn't #5497 supersede this?

@uecasm
Copy link

uecasm commented Jun 10, 2022

That one's a non-starter for me because it requires an extra keyword/decoration on the class being extended. The whole point of extension methods is to extend things that the original designer didn't think of and didn't make extensible any other way.

@LokiMidgard
Copy link
Contributor

LokiMidgard commented Jun 11, 2022

That one's a non-starter for me because it requires an extra keyword/decoration on the class being extended. The whole point of extension methods is to extend things that the original designer didn't think of and didn't make extensible any other way.

@uecasm Do you mean #5497? I think it does not…


From the proposal

public class DataObject
{
    public DataObject this[string member] { get; set; }
    public string AsString() { }
    public IEnumerable<DataObject> AsEnumerable();
    public int ID { get; }
}

gets extended by

public extension JsonDataObject : DataObject
{
    public string ToJson() { … this … }
    public static DataObject FromJson(string json) {}
}

I don't see any special keyword. I havn't read the howl thread, so maybe I skiped something

@uecasm
Copy link

uecasm commented Jun 11, 2022

Oh, maybe I overlooked something then. It looked to me like you could only extend roles.

@LokiMidgard
Copy link
Contributor

No, as I understood, Roles are a construct to add Interfaces to external types (and maybe more), but are not nessesary to extend something.

@nathan130200
Copy link

Omg, no ETA for this best feature for C#? 😞

@brianjlacy
Copy link

What I'm hoping for with extension properties specifically is something like

public static class MyStringExtensions {
    public static string Author {
        get(this string);
        set(this string);
    }

    public static string Half {
        get(this string baseStr) => baseStr.Substring(baseStr.Length / 2);
    }
}

The examples aren't stellar, but I feel like the syntax feels natural and intuitive given what devs are used to with extension methods.

@HaloFour
Copy link
Contributor

@brianjlacy

Still very much in design, but the current thought is that you declare an "extension type", which can support most members:

#5497
https://github.com/dotnet/csharplang/blob/main/proposals/extensions.md

implicit extension MyStringExtensions for string {
    public string Half {
        get => Substring(Length / 2);
    }
}

@nathan130200
Copy link

nathan130200 commented Feb 17, 2024

I still prefer classic extension syntax, but there's some problems:

public extension MyGuidExtensions for System.Guid // name or qualified name
{
	public string ToBase64()
	{
		// this = treated as Guid instance, internal/private fields should 
		// be visible only in same project or solution or 
		// else use reflection instead.
		// but... "this" can be omitted? this is a good question
		return Convert.ToBase64String(this.ToByteArray());
	}
}

public extension DynIntExtensions for Int32 // force qualified name instead keyword?
{
	public int SevenBitLength
	{
		get
		{
			int numBytes = 0;
			var value = this; // make a copy from self, but how?
			
			do
			{
				value >>>= 7;
				numBytes++;
			}
			while(value > 0);

			return numBytes;
		}
	}
}

// Sample code:

var offset = 0;
int packetId = reader.ReadVarInt32();
var bytesToSkip = packetId.SevenBitLength; 
offset += bytesToSkip

Or something similar to what the Rust language does with the impl keyword and traits Rust: Traits & Impl

@CyrusNajmabadi
Copy link
Member

@nathan130200 i'm not quite certain i'm getting what the problems are that you're referring to.

@HaloFour
Copy link
Contributor

@nathan130200

// name or qualified name
// force qualified name instead keyword?

I would expect that you could use the name (or alias) of any type in scope, including the keywords for the primitive types like int.

// this = treated as Guid instance

My gut feeling is that within an "extension" type that this would refer to the extended type, e.g. System.Guid, and not the extension itself.

var value = this; // make a copy from self, but how?

If the above is true, then I would expect that this would just work.

These are good questions for the extensions working group.

@nathan130200
Copy link

nathan130200 commented Feb 17, 2024

@CyrusNajmabadi

It's not really a problem but the comments I put in the code. Some things don't seem so objective. For example, "this being treated as an instance of Guid".

@HaloFour

My gut feeling is that within an "extension" type that this would refer to the extended type, e.g. System.Guid, and not the extension itself.

So in this case, the compiler could understand that if we are inside the extension, it will treat this as if it were the instance of the object itself.

Or better yet, add a keywork called self that refers to the object we are extending, leaving this for the extension itself, because the extension would not be a class or a struct, it would just be a way of extending the fields, methods , properties and events, or can we consider the "extension" type as a reference/value type?

I think that these extensions are just a way of instructing the compilation and not a class in itself. Because what happens if you want to cast the extension? I don't think it would make sense to cast to a type that doesn't actually inherit, it's just "inheritance" at runtime, I think that's the right word.

I think that these extensions are just a way of instructing the compiler/IDE and not a class in itself. Because what happens if you want to cast the extension? I don't think it would make sense to cast to a type that doesn't actually inherit, it's just "inheritance" at runtime, I think that's the right word.

Ah, unless it is implemented, there is some way to do an implicit/explicit cast for the extension.

@CyrusNajmabadi
Copy link
Member

"this being treated as an instance of Guid".

I'm not seeing the issue. Why would that not be hte case?

Or better yet, add a keywork called self that refers to the object we are extending, leaving this for the extension itself,

I don't see the value of that. if self refers to the object being extended... then the current extension still applies to it, since the extension applies to all instances of the extended type.

or can we consider the "extension" type as a reference/value type?

Neither? The extension is just a set of members on a particular type. That partial type can be any type (unless we choose to limit choices there in some fashion).

It's like asking "is an extension method a reference or value type?". It's neither. It's a method. An "extension" then is just a collection of those members (all extending a particular type), and it's not limited to just methods. It can be things like constructors, properties, etc.

@CyrusNajmabadi
Copy link
Member

As an example, ignoring that your code has a property, you could imagine that:

public extension DynIntExtensions for Int32 // force qualified name instead keyword?
{
	public int SevenBitLength
	{
		get
		{
			int numBytes = 0;
			var value = this; // make a copy from self, but how?
			
			do
			{
				value >>>= 7;
				numBytes++;
			}
			while(value > 0);

			return numBytes;
		}
	}
}

Is just shorthand for:

public static class DynIntExtensions 
{
	public int GetSevenBitLength(this int @this)
	{
		int numBytes = 0;
		var value = @this;
			
		do
		{
			value >>>= 7;
			numBytes++;
		}
		while(value > 0);

		return numBytes;
	}
}

All that 'implicit extensions' (as a feature) is doing is:

  1. allowing you to group tons of related extension members together (instead of having to say what is being extended per member).
  2. allowing you to provide more than just 'extension methods'. So things like 'extension properties' or 'extension operators' are now all possible.

@nathan130200
Copy link

nathan130200 commented Feb 17, 2024

What could be done in this case is:

  • Support for extension members (fields, methods, events, etc.)
  • Allow static types to receive these extensions members as well.

In the case of fields, events and properties, perhaps an attribute that makes the compiler identify that that extension "method" can be invoked as a member.

Just like you used GetSevenBitLength in the example, add an attribute [ExtendedMember], [ExtendedProperty] or something better to identify.

@CyrusNajmabadi
Copy link
Member

You can't do extension fields as extensions have no way to add state to anything.

@CyrusNajmabadi
Copy link
Member

Allow static types to receive these extensions members as well.

They can.

@CyrusNajmabadi
Copy link
Member

perhaps an attribute that makes the compiler identify that that extension "method" can be invoked as a member.

That's what an extension already does. See how extension methods are encoded into a dll. It's just an attribute that marks then as such.

@CyrusNajmabadi
Copy link
Member

or something better to identify

We don't need that in source as the source already indicates this by use of the extension keyword.

@weltkante
Copy link

You can't do extension fields as extensions have no way to add state to anything.

The "workaround" would be an extension property returning a ref-value, which is "almost as good" as a field. It can lookup or create a container based on the instance so it has someplace to store the data, e.g. a ConditionalWeakTable, and then can return a reference into that data container.

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

No branches or pull requests