Why is Func<T> ambiguous with Func<IEnumerable<T>>?

This one's got me flummoxed, so I thought I'd ask here in the hope that a C# guru can explain it to me.

Why does this code generate an error?

class Program
{
    static void Main(string[] args)
    {
        Foo(X); // the error is on this line
    }

    static String X() { return "Test"; }

    static void Foo(Func<IEnumerable<String>> x) { }
    static void Foo(Func<String> x) { }
}

The error in question:

Error
    1
    The call is ambiguous between the following methods or properties:
'ConsoleApplication1.Program.Foo(System.Func<System.Collections.Generic.IEnumerable<string>>)' and 'ConsoleApplication1.Program.Foo(System.Func<string>)'
    C:\Users\mabster\AppData\Local\Temporary Projects\ConsoleApplication1\Program.cs
    12
    13
    ConsoleApplication1

It doesn't matter what type I use - if you replace the "String" declarations with "int" in that code you'll get the same sort of error. It's like the compiler can't tell the difference between Func<T> and Func<IEnumerable<T>>.

Can someone shed some light on this?


Solution 1:

OK, here's the deal.

The short version:

  • The ambiguity error is, bizarrely enough, correct.
  • The C# 4 compiler also produces a spurious error after the correct ambiguity error. That appears to be a bug in the compiler.

The long version:

We have an overload resolution problem. Overload resolution is extremely well specified.

Step one: determine the candidate set. That's easy. The candidates are Foo(Func<IEnumerable<String>>) and Foo(Func<String>).

Step two: determine which members of the candidate set are applicable. An applicable member has every argument convertible to every parameter type.

Is Foo(Func<IEnumerable<String>>) applicable? Well, is X convertible to Func<IEnumerable<String>?

We consult section 6.6 of the spec. This part of the specification is what we language designers call "really weird". Basically, it says that a conversion can exist, but using that conversion is an error. (There are good reasons why we have this bizarre situation, mostly having to do with avoiding future breaking changes and avoiding "chicken and egg" situations, but in your case we are getting somewhat unfortunate behaviour as a result.)

Basically, the rule here is that a conversion from X to a delegate type with no parameters exists if overload resolution on a call of the form X() would succeed. Clearly such a call would succeed, and therefore a conversion exists. Actually using that conversion is an error because the return types don't match, but overload resolution always ignores return types.

So, a conversion exists from X to Func<IEnumerable<String>, and therefore that overload is an applicable candidate.

Obviously for the same reason the other overload is also an applicable candidate.

Step Three: We now have two applicable candidates. Which one is "better"?

The one that is "better" is the one with the more specific type. If you have two applicable candidates, M(Animal) and M(Giraffe) we choose the Giraffe version because a Giraffe is more specific than an Animal. We know that Giraffe is more specific because every Giraffe is an Animal, but not every Animal is a Giraffe.

But in your case neither type is more specific than the other. There is no conversion between the two Func types.

Therefore neither is better, so overload resolution reports an error.

The C# 4 compiler then has what appears to be a bug, where its error recovery mode picks one of the candidates anyways, and reports another error. It's not clear to me why that is happening. Basically it is saying that error recovery is choosing the IEnumerable overload, and then noting that the method group conversion produces an untenable result; namely, that string is not compatible with IEnumerable<String>.

The whole situation is rather unfortunate; it might have been better to say that there is no method-group-to-delegate conversion if the return types do not match. (Or, that a conversion that produces an error is always worse than a conversion that does not.) However, we're stuck with it now.

An interesting fact: the conversion rules for lambdas do take into account return types. If you say Foo(()=>X()) then we do the right thing. The fact that lambdas and method groups have different convertibility rules is rather unfortunate.

So, summing up, the compiler is actually a correct implementation of the spec in this case, and this particular scenario is an unintended consequence of some arguably unfortunate spec choices.

Solution 2:

Your code requires "magic" to take place twice, once to convert from the named method group to a delegate, and once to perform overload resolution.

Despite the fact that you have only one method named X, the compiler rules are built for the case when there are multiple.

In addition, since delegates don't have to match the method signature exactly, the complexity is further increased. On top of that, any given method can be converted to an unlimited number of different delegate types with identical signatures.

Your particular case looks straightforward enough, but the general case is very very hard, so the language doesn't allow it.

If you do part of the work by hand, you'll solve the problem. e.g.

Func<string> d = X;
Foo(d);

should compile just fine.