Method group conversion rules could not be implemented
The current rules for "method group conversions" say that it should bind like a method invocation: "A single method M is selected corresponding to a method invocation of the form E(A)".
The current rules for "member lookup" know to ignore non-invokable members when we're dealing with an invocation:
- "If a member is a method or event, or if it is a constant, field or property of either a delegate type or the type dynamic (, then the member is said to be invocable."
- "Next, if the member is invoked, all non-invocable members are removed from the set."
Those two rules cannot be implemented as-is, and the compiler does not in fact implement them.
The problem is that when binding a delegate conversion like Action a = o.M; we don't know that we're in a method group conversion until we've bound the right-hand side, but we can't bind the right-hand side without knowing whether we're being invoked or not.
Here are two scenarios to illustrate the problem. Per the spec, I would expect System.Action a = c.M; and c.M() to bind the same way, without error, in both of those scenarios. But in fact, only the invocation syntax c.M() binds successfully.
C c = null;
System.Action a = c.M; // error CS0029: Cannot implicitly convert type 'int' to 'System.Action'
c.M(); // okay
public class Base
{
public void M() { }
}
public class C : Base
{
public int M; // non-invokable field
}
C c = null;
System.Action a = c.M; // error CS0029: Cannot implicitly convert type 'int' to 'System.Action'
c.M(); // okay
public static class E
{
public static void M(this C c) { }
}
public class C
{
public int M; // non-invokable field
}
Tagging @MadsTorgersen @BillWagner @alekseyts @jjones
Can this not be fixed if “member is invoked” is replaced by “member is in a context requiring a function” – to know the member is invoked requires knowledge of the context of the member lookup, so such a change would “just” require a compiler to look a little more into the context. (Note some compilers for other languages already do this, but it's “just” as I expect it might be non-trivial for the current C# compiler(s)).
The actual error produced in the example is down to hiding, there are two bullets in play:
- Next, if the member is invoked, all non-invocable members are removed from the set.
- Next, members that are hidden by other members are removed from the set. For every member
S.Min the set, whereSis the type in which the memberMis declared, the following rules are applied:
If the M's in Base & C are switched around the example compiles with just a warning there is no new on the M in C even though the current compiler does not know that the member lookup is happening in a context requiring a function.
Here be dragons? The above suggest fix is really a patch on what could well be more issues in this area as C# has expanded, it may cause other issues – I’ve not checked.