Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Proposal: Extension Methods on Method Groups #5481

Closed
SLaks opened this issue Dec 26, 2016 · 13 comments
Closed

Proposal: Extension Methods on Method Groups #5481

SLaks opened this issue Dec 26, 2016 · 13 comments

Comments

@SLaks
Copy link

SLaks commented Dec 26, 2016

Can you please allow extension methods to be called on method groups or lambdas via method group conversion to a delegate or expression type?

You should then also allow deconstructing assignments of method groups & especially lambdas (via public static void Deconstruct(this Expression<Func<...>>, out ...)).

Benefits

This would allow a number of useful extension methods, such as

var inTemp = Path.Join.Curry(Path.GetTempPath());
var toUpperCase = "".ToUpper.RemoveTarget();  // or ToOpenDelegate() or UnCurry()
var isValid = Path.IsPathRooted.And(new RegEx(@"...").IsMatch);

stuff.Select((o => ...).WithErrorHandling());

(object target, MethodInfo method) = "".ToUpper;
(ParameterExpression param, Expression body) = (string p) => p.Whatever();

Risks

Very little; none of this is currently valid syntax, so this should not be a breaking change.
This would remove the following errors:

  • CS0023: Operator '.' cannot be applied to operand of type 'lambda expression'
  • CS0119: 'C.M(Action)' is a method, which is not valid in the given context
  • CS8131: Deconstruct (sic) assignment requires an expression with a type on the right-hand-side

This could be a bit confusing if a method group and extension method have multiple matching overloads, but that issue already exists when calling non-extension methods.

Implementation

All of these examples are already callable without extension syntax, using delegate conversions on the first parameter. Just allow these conversions when selecting extension methods too.

@alrz
Copy link
Contributor

alrz commented Dec 27, 2016

(o => ...).WithErrorHandling()

Related: dotnet/roslyn#3990

@SLaks
Copy link
Author

SLaks commented Dec 27, 2016

@sharwell Oops; fixed

@aluanhaddad
Copy link

@alrz yes dotnet/roslyn#3990 would benefit from this, but this proposal stands on its own.

@alrz
Copy link
Contributor

alrz commented Dec 28, 2016

@aluanhaddad Yes, but that particular example that I mentioned requires the lambda to have a default type.

@aluanhaddad
Copy link

aluanhaddad commented Dec 28, 2016

@alrz I see what you mean. Because the conversion target would need to be manifest for method selection to even occur.
I think there might be other ways that this could be made to work. Basically compiler black magic that considers the possible target types having the bespoke extension method, but it would be better if there were in fact a default delegate type, or default families of delegate types.

In the black magic case (which all is in my head and I may well be insane), we are essentially saying:
take the lambda expression and the invocation of WithErrorHandling and perform a syntactic transformation on the expression such that (o => ...).WithErrorHandling is viewed, for this purpose, as DelegateExtensions.WithErrorHandling(o => ...) and bind that.
Or, another way of looking at it, are their any types to which (o => ...) is convertible having an extension method WithErrorHandling taking a single argument of that type as their first parameter.

@alrz
Copy link
Contributor

alrz commented Dec 28, 2016

I think that's a better idea because the compiler cannot know about all possible delegate types (unless dotnet/roslyn#3990 looks up for other delegate types in scope if Action and Func were not applicable which is unlikely).

@gafter
Copy link
Member

gafter commented Dec 30, 2016

If I understand correctly, the proposal is to add the bold text below to the spec section 7.6.6.2:

An extension method Ci.Mj is eligible if:
Ci is a non-generic, non-nested class
• The name of Mj is identifier
Mj is accessible and applicable when applied to the arguments as a static method as shown above
• An implicit identity, reference, method group, anonymous function, or boxing conversion exists from expr to the type of the first parameter of Mj.

@gafter
Copy link
Member

gafter commented Jan 5, 2017

@SLaks Thanks, edited.

@SLaks
Copy link
Author

SLaks commented Jan 6, 2017

Yes; that's exactly what I mean.

It would make scanning for extension methods a bit more costly (since it has to consider more conversions); I don't know how bad that is.

@giggio
Copy link

giggio commented Jan 8, 2019

This would be beautiful, and would make the language more functional. The WithErrorHandling() would be very appealing. The currying too.

Is it being considered for v8?

@CyrusNajmabadi
Copy link
Member

@giggio It is not.

@hypehuman
Copy link

hypehuman commented Aug 22, 2020

Here is a use case for this, driven by #424

public static TResult Splat<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> method, (T1, T2, T3) args)
{
    return method(args.Item1, args.Item2, args.Item3);
}

void example()
{
    var input = (1d, 1d, 1d);
    (double, double) output;

    // These both work:
    output = Extensions.Splat(Globe.ConvertToSpherical, input);
    output = ((Func<double, double, double, (double, double)>)Globe.ConvertToSpherical).Splat(input);

    // This doesn't compile. Error:
    // CS0119	'Globe.ConvertToSpherical(double, double, double)' is a method, which is not valid in the given context
    output = Globe.ConvertToSpherical.Splat(input);
}

(double theta, double psi) ConvertToSpherical(double x, double y, double z)
{
    throw new NotImplementedException();
}

@Mafii
Copy link

Mafii commented Nov 23, 2021

It would also be nice if we could do the following (automatically find the best matching type (action) for method groups):

value switch 
{
    value => value.SomeMethod,
}();

// or, if there is no native suport for this, we could at least define a method like this:

private Action AsAction(Action action)
    => action;

// and then do this:

value switch 
{
    value => value.SomeMethod.AsAction(),
}();

Because at the moment, if I have a task, I can use expression switches for "void like" meaning the void task returning nothing when awaited and have the await before the expression.

There's no reason for that limitation with void - just accepting void as a return type for an expression switch would be nice too, but I guess that's not realistic from what I've read in other issues.

Void-Task example that compiles (showing that in theory, void expressions would make just as much sense):

private Task Run(int value) => Task.CompletedTask;
private Task Run2() => Task.CompletedTask;

await (value switch 
{
    3 => Run(value),
    _ => Run2(),
});

But this does not work:

private void Run(int value) => NoOperation();
private void Run2() => NoOperation();

// Doesn't work (statement not allowed there)
value switch 
{
    3 => Run(value),
    _ => Run2(),
};

// Doesn't work (void can not be assigned to variable)
_ = value switch 
{
    3 => Run(value),
    _ => Run2(),
};

// Doesn't work (no best type found)
value switch 
{
    3 => () => Run(value),
    _ => Run2,
}();

// Does work, but is really annoying and adds noise for no reason, and you can't use expression bodies anymore if you're writing a method
Action runner = value switch 
{
    3 => () => Run(value),
    _ => Run2,
};

runner();

Allowing Extension methods on Method groups would give some flexibility, and automatically detecting that Action is the best type here (not only when running Extension methods like the proposal) would be an even bigger improvement over status quo.

Are there any updates wheter this is considered as a champion for v11 or v12?

@333fred 333fred transferred this issue from dotnet/roslyn Nov 29, 2021
@dotnet dotnet locked and limited conversation to collaborators Nov 29, 2021
@333fred 333fred closed this as completed Nov 29, 2021

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants