I'm having trouble getting the compiler to resolve the correct overload for an extension method. The best way for me to explain is with a little code. Here's a LINQPad script that demonstrates the problem. This won't compile because of the problem I'm having:
void Main(){
new Container<A>().Foo(a=>false);
}
interface IMarker{}
class A : IMarker{
public int AProp{get;set;}
}
class B : IMarker{
public int BProp{get;set;}
}
class Container<T>{}
static class Extensions{
public static void Foo<T>(this T t, Func<T, bool> func)
where T : IMarker{
string.Format("Foo({0}:IMarker)", typeof(T).Name).Dump();
}
public static void Foo<T>(this Container<T> t, Func<T, bool> func){
string.Format("Foo(Container<{0}>)", typeof(T).Name).Dump();
}
}
There error I get is:
The call is ambiguous between the following methods or properties: '
Extensions.Foo<Container<A>>(Container<A>, System.Func<Container<A>,bool>)
' and 'Extensions.Foo<A>(Container<A>, System.Func<A,bool>)
'
It seems to me that it's not ambiguous at all. The first method won't accept a Container<T>
, only an IMarker
. It seems like the generic constraints aren't assisting in overload resolution, but in this version of the code, they do seem to be:
void Main(){
new A().Bar();
new A().Foo(a=>a.AProp == 0);
new A().Foo(a=>false); // even this works
new A().Foo(a=>{
var x = a.AProp + 1;
return false;
});
new Container<A>().Bar();
new Container<A>().Foo(a=>a.AProp == 0);
new Container<A>().Foo(a=>{
var x = a.AProp + 1;
return false;
});
}
interface IMarker{}
class A : IMarker{
public int AProp{get;set;}
}
class B : IMarker{
public int BProp{get;set;}
}
class Container<T>{}
static class Extensions{
public static void Foo<T>(this T t, Func<T, bool> func)
where T : IMarker{
string.Format("Foo({0}:IMarker)", typeof(T).Name).Dump();
}
public static void Foo<T>(this Container<T> t, Func<T, bool> func){
string.Format("Foo(Container<{0}>)", typeof(T).Name).Dump();
}
public static void Bar<T>(this T t) where T : IMarker{
string.Format("Bar({0}:IMarker)", typeof(T).Name).Dump();
}
public static void Bar<T>(this Container<T> t){
string.Format("Bar(Container<{0}>)", typeof(T).Name).Dump();
}
}
This compiles and produces the expected results:
Bar(A:IMarker)
Foo(A:IMarker)
Foo(A:IMarker)
Foo(A:IMarker)
Bar(Container<A>)
Foo(Container<A>)
Foo(Container<A>)
It seems to only have a problem when I don't reference the lambda parameter in the lambda expression, and then only with the Container<T>
class. When calling Bar
, there is no lambda, and it works fine. When calling Foo
with the return value based on the lambda parameter, it works fine. Even if the lambda's return value is the same as the one in the example that doesn't compile, but the lambda parameter is referenced by a dummy assignment, it works.
Why does it work in these cases but not in the first? Am I doing something wrong, or have I found a compiler bug? I've confirmed the behavior in both C# 4 and C# 6.
Oh, I got it after re-reading my own answer! Nice question =)
The overload does not work because it does not take constraint where T:IMaker
into account while resolving overload (constraint is not a part of method signature). When you reference a parameter in lambda you (can) add a hint to the compiler:
This works:
new Container<A>().Foo(a => a.AProp == 0);
because here we do hint that a:A;
This does not work even with a reference to parameter:
new Container<A>().Foo(a => a != null);
because there is still not enough information to infer the type.
As far as I understand the specification, in "Foo scenario" the inference can fail on the second (Func) argument thus making the call ambiguous.
Here's what spec (25.6.4) says:
Type inference occurs as part of the compile-time processing of a method invocation (§14.5.5.1) and takes place before the overload resolution step of the invocation. When a particular method group is specified in a method invocation, and no type arguments are specified as part of the method invocation, type inference is applied to each generic method in the method group. If type inference succeeds, then the inferred type arguments are used to determine the types of arguments for subsequent overload resolution.
If overload resolution chooses a generic method as the one to invoke, then the inferred type arguments are used as the runtime type arguments for the invocation. If type inference for a particular method fails, that method does not participate in overload resolution. The failure of type inference, in and of itself, does not cause a compile-time error. However, it often leads to a compile-time error when overload resolution then fails to find any applicable methods.
Now lets get to pretty straightforward "Bar scenario". After type inference we will get only one method, because only one is applicable:
Bar(Container<A>)
for new Container<A>()
(does not implement IMaker)Bar(A)
for new A()
(is not a Container)And here's the ECMA-334 specification, just in case. P.s. I'm not 100% sure that I got it right, but I prefer to think that I grasped the essential part.