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

Overload resolution and inferred lambda / method group delegate type: potential breaking change #4674

Closed
cston opened this issue Apr 20, 2021 · 2 comments

Comments

@cston
Copy link
Member

cston commented Apr 20, 2021

The lambda improvements proposal includes inferring delegate types for lambdas and method groups, creating the possibility of breaking changes in overload resolution.

Example 1

Two overloads in the default method group M, one with a generic parameter type, the other with a delegate type parameter. In C#9, M(F) binds to M(Action<string> a).

If a delegate type is inferred for F of Action<object>, overload resolution will bind to the generic overload M<Action<object>>(Action<object> t) instead.

using System;
 
class Program
{
    static void M<T>(T t) { }
    static void M(Action<string> a) { }
    
    static void F(object o) { }
    
    static void Main()
    {
        M(F); // C#9: M(Action<string>)
    }
}

See sharplab.io

Example 2

Two overloads in the method group c.M, one with an object type parameter on the containing type, the other with a delegate type parameter as an extension method. In C#9, the calls to c.M(expr) bind to the extension method.

If delegate types are inferred for the method group and lambda expression, those expressions are implicitly convertible to object and overload resolution will bind the calls to the overload on the containing type instead.

using System;

class Program
{
    static void Main()
    {
        var c = new C();
        c.M(Main);      // C#9: E.M(object x, Action y)
        c.M(() => { }); // C#9: E.M(object x, Action y)
    }
}

class C
{
    public void M(object y) { }
}

static class E
{
    public static void M(this object x, Action y) { }
}

See sharplab.io

Possible solution

  1. Prefer binding lambda or method group arguments using the target type (the parameter type) rather than the inferred delegate type of the lambda expression or method group.
  2. If extension methods should be considered, and binding of the method group from the containing type or any inner extension method scope used the inferred type for lambda or method group arguments, continue searching outer extension method scopes for an overload that binds using the parameter type.
@cston
Copy link
Member Author

cston commented May 20, 2021

Example 3

In C#9, M((string s) => { }) binds to M(StringAction a).

If a delegate type is inferred for the lambda expression, the call is ambiguous with M<Action<string>>(Action<string> t) and M(StringAction a).

delegate void StringAction(string arg);
 
class Program
{
    static void M<T>(T t) { }
    static void M(StringAction a) { }
    
    static void Main()
    {
        M((string s) => { }); // C#9: M(StringAction)
    }
}

@333fred 333fred closed this as completed Aug 17, 2021
@cston
Copy link
Member Author

cston commented Aug 17, 2021

Discussed at LDM-2021-04-21.

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

No branches or pull requests

2 participants