Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
386 views
in Technique[技术] by (71.8m points)

c# - 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:UsersmabsterAppDataLocalTemporary ProjectsConsoleApplication1Program.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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...