Roslyn: Proposal: Extension Methods on Method Groups

Created on 26 Dec 2016  Â·  12Comments  Â·  Source: dotnet/roslyn

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

```C#
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.

0 - Backlog Area-Language Design Language-C#

Most helpful comment

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.

All 12 comments

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

Related: #3990

@sharwell Oops; fixed

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

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

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

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

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.

@SLaks Thanks, edited.

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.

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?

@giggio It is not.

Here is a use case for this, driven by https://github.com/dotnet/csharplang/issues/424

```C#
public static TResult Splat(this Func 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();
}
```

Was this page helpful?
0 / 5 - 0 ratings