Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics, overload resolution and delegates (sorry, can't find a better title) [duplicate]

Possible Duplicate:
Why is Func<T> ambiguous with Func<IEnumerable<T>>?

I noticed a very weird overload resolution issue with generics...

Consider the following methods:

static void Foo<TSource>(TSource element, Func<TSource, int> selector)
{
    "int".Dump();
}

static void Foo<TSource>(TSource element, Func<TSource, double> selector)
{
    "double".Dump();
}

static T Identity<T>(T value)
{
    return value;
}

(C# 4, tested in LINQPad)

If I try to call Foo with a lambda expression as the selector, everything works fine:

Foo(42, x => x); // prints "int"

But if I replace x => x with Identity, the compiler can't decide between the 2 Foo overloads:

Foo(42, Identity);
// The call is ambiguous between the following methods or properties:
// 'UserQuery.Foo<int>(int, System.Func<int,int>)' and
// 'UserQuery.Foo<int>(int, System.Func<int,double>)'

How can the second overload be a valid candidate ? Type inference correctly determines that TSource is int, so the T parameter for the Identity method has to be int as well, so the return type has to be int too... Identity could be a Func<int,int> or a Func<double,double>, but not a Func<int,double>!

And it gets worse! Even if I specify all type parameters explicitly, I still get the same error:

Foo<int>(42, Identity<int>); // The call is ambiguous...

How can there be any ambiguity here? As far as I can tell, there is no way the overload that takes a Func<int,double> can be a candidate. I guess the explanation must be somewhere in the specifications, but I can't find the relevant bit... or it might be a bug in the compiler, but I guess it's unlikely.

Note that it does work if I explicitly create the delegate:

Foo(42, new Func<int, int>(Identity)); // prints "int"

So, could someone explain what's going on here? Also, why does it work with a lambda but not with a method group?

like image 972
Thomas Levesque Avatar asked Jan 05 '11 00:01

Thomas Levesque


2 Answers

Isn't it simply because the return type isn't part of the method's signature?

The fact that the Identity<T> method's argument type and return type are guaranteed to be the same isn't taken into account by the compiler when attempting to decide which overload of Foo<TSource> is required. If the return type isn't considered then Identity<int> could equally be convertible to Func<int, int>, Func<int, double> or Func<int, anything>.

like image 55
LukeH Avatar answered Nov 07 '22 14:11

LukeH


I think that LukeH is correct. However, to answer the second bit of your question: the delegate of the lambda will already have all types filled in (e.g. always be a Func<int, int> if TSource is an int), which is why there is no ambiguity in that case. It's not like a function signature where the return type is ignored.

like image 35
Lucero Avatar answered Nov 07 '22 13:11

Lucero