Suppose I had this in C#:
class OverloadTest
{
void Main()
{
CallWithDelegate(SomeOverloadedMethod);
}
delegate void SomeDelegateWithoutParameters();
delegate void SomeDelegateWithParameter(int n);
void CallWithDelegate(SomeDelegateWithoutParameters del) { }
void CallWithDelegate(SomeDelegateWithParameter del) { }
void SomeOverloadedMethod() { }
void SomeOverloadedMethod(int n) { }
}
Of course, this does not compile, because the line CallWithDelegate(SomeOverloadedMethod);
is ambiguous.
Now, suppose there was only one CallWithDelegate(SomeDelegateWithoutParameter del)
function (no overloads). In this case, there would be no ambiguity, because, from what seems to be happening, the compiler can look at the parameter type and discard SomeOverloadedMethod(int n)
from the candidate list (since it can only take a SomeDelegateWithoutParameters
), and so it compiles.
I don't intend to write code like this; this is just out of curiosity, from a compiler writer point-of-view. I couldn't find an answer about this, since it is quite confusing to put into words.
I'd like to know if there is any way in C# to disambiguate that call in Main()
in the example given, so that it would compile. How can you specify it so that it resolves into CallWithDelegate(SomeDelegateWithoutParameters del)
being passed SomeOverloadedMethod()
, or CallWithDelegate(SomeDelegateWithParameter del)
being passed SomeOverloadedMethod(int n)
?
There are several ways to disambiguate overload resolution of method groups.
CallWithDelegate((SomeDelegateWithoutParameters)SomeOverloadedMethod);
CallWithDelegate((SomeDelegateWithParameter)SomeOverloadedMethod);
This disambiguates the overload. That's pretty uncommon syntax in the wild, but it works (C# 5 spec §6.6 Method group conversions):
As with all other implicit and explicit conversions, the cast operator can be used to explicitly perform a method group conversion.
[...]
Method groups may influence overload resolution, and participate in type inference.
CallWithDelegate(new SomeDelegateWithoutParameters(SomeOverloadedMethod));
CallWithDelegate(new SomeDelegateWithParameter(SomeOverloadedMethod));
This is the same as the previous method without the syntactic sugar. See the spec at §7.6.10.5 Delegate creation expressions for more details.
The binding-time processing of a delegate-creation-expression of the form
new D(E)
, whereD
is a delegate-type andE
is an expression, consists of the following steps:
- If
E
is a method group, the delegate creation expression is processed in the same way as a method group conversion (§6.6) fromE
toD
.[...]
There's even an example closely related to your question:
As described above, when a delegate is created from a method group, the formal parameter list and return type of the delegate determine which of the overloaded methods to select. In the example
delegate double DoubleFunc(double x); class A { DoubleFunc f = new DoubleFunc(Square); static float Square(float x) { return x * x; } static double Square(double x) { return x * x; } }
the
A.f
field is initialized with a delegate that refers to the secondSquare
method because that method exactly matches the formal parameter list and return type ofDoubleFunc
. Had the secondSquare
method not been present, a compile-time error would have occurred.
CallWithDelegate(() => SomeOverloadedMethod());
CallWithDelegate(i => SomeOverloadedMethod(i));
CallWithDelegate((int i) => SomeOverloadedMethod(i)); // Explicit types, if needed
This form is not ambiguous but it has an indirection (the lambda is called, and it then calls the target method). This may get optimized by the JIT though, and it most probably won't have a visible performance impact anyway.
CallWithDelegate(delegate() { SomeOverloadedMethod(); });
CallWithDelegate(delegate(int i) { SomeOverloadedMethod(i); });
This is equivalent to the lambda calls, but it uses the bulkier (and older) delegate
syntax.
If you'd like to know the exact overload resolution rules, they're described in the spec in §7.5.3 Overload resolution.