views:

364

answers:

5

Suppose I have

public static List<T2> Map<T,T2>(List<T> inputs, Func<T, T2> f)
{
    return inputs.ConvertAll((x) => f(x));
}

private int Square(int x) { return x*x; }

public void Run()
{
    var inputs = new List<Int32>(new int[]{2,4,8,16,32,64,128,256,512,1024,2048});

    // this does not compile
    var outputs = Map(inputs, Square); 

    // this is fine
    var outputs2 = Map<Int32,Int32>(inputs, Square);

    // this is also fine (thanks, Jason)
    var outputs2 = Map<Int32,Int32>(inputs, (x)=>x*x);

    // also fine
    var outputs2 = Map(inputs, (x)=>x*x);
}

Why does it not compile?

EDIT: The error is:

error CS0411: The type arguments for method 'Namespace.Map<T,T2>(System.Collections.Generic.List<T>, System.Func<T,T2>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Why do I have to specify the type of the Map() function? Can it not infer this from the passed Func<T> ? (in my case, Square)


Is the answer the same as for
C# 3.0 generic type inference - passing a delegate as a function parameter ?

+2  A: 

The inference fails at inferring the type of the delegate, not the list:

// this is also fine
var outputs3 = Map(inputs, new Func<int, int>(Square));

// more calls that compile correctly
var outputs4 = Map(inputs, x => Square(x));

var outputs5 = Map(inputs, x => x * x);

Func<int, int> t = Square;
var outputs6 = Map(inputs, t);

I don't know why, though - perhaps there's just no implicit typecast from the signature of Square to Func<Int32, Int32>? It seems strange that Func<int, int> t = Square; is valid, but the compiler can't make the leap on its own... Bug, maybe?

Dathan
Read the error message. The type arguments for method '`[...].Map<T,T2>(System.Collections.Generic.List<T>, System.Func<T,T2>)`'. It can not figure out what the generic type parameters are. It can resolve `T` because `inputs` is a `List<int>` so it knows that `T` is `int`. Thus it can not resolve `T2`. See my answer.
Jason
I don't understand how this answer has upvotes; it is wrong. It's not failing at inferring the type of a delegate. It's that it is unable to use a method group to infer one of the type parameters.
Jason
The delegate requires T2 as a generic parameter, and the compiler can't infer the type of T2 (in accordance with the spec, as you point out), it follows that the compiler can't infer the type of the delegate. While it's not the root cause - your explanation is clearly the correct one - it's not technically incorrect.
Dathan
A: 

The following also works; I don't know why:

var outputs = Map(inputs, i => Square(i));
John Saunders
Because unlike method groups, lambda expressions do play a role in type parameter inference. cf. http://msdn.microsoft.com/en-us/library/ms364047(VS.80).aspx#cs3spec_topic4
Jason
A: 

Upon a little digging, I've found your suspicion about the other answer is correct. Here's what the C# 3.0 specification says:

7.4.2.1 - For each of the method arguments Ei: If Ei is an anonymous function or method group, an explicit parameter type inference (7.4.2.7) is made... 7.4.2.7 - ...If E is an explicitly typed anonymous function with parameter types U1…Uk and T is a delegate type with parameter types V1…Vk then for each Ui an exact inference (§7.4.2.8) is made from Ui for the corresponding Vi.

In other words, anonymous methods and method groups (which Square is), can only infer the parameter types explicitly. I think the justification at the end of the answer you referred to sums it up nicely. Because a type inference cannot always be made implicitly from a method group, the compiler does not even attempt it, per the specification.

Eric Mickelsen
+1  A: 

The reason this does not work is for c# to do type inference on a method, it has to know the delegate type at the other end of the conversion. But at this point in time, the target delegate type is still not fully known - only T (int) is known, T2 is still unresolved.

Func<int, int> f = Square;
//works because we provided the destination type
//of the conversion from Square to delegate

Map(inputs, i => Square(i));
//works because the lambda follows the actual method call
//and determines its own return type
Rex M
+4  A: 

From your error message:

The type arguments for method '[...].Map<T,T2>(System.Collections.Generic.List<T>, System.Func<T,T2>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Note that the error message says that it can not figure out the type arguments. That is, it is having trouble resolving one of the type parameters T or T2. This is because of §25.6.4 (Inference of type arguments) of the specification. This is the part of the specification the deals with inferring generic type parameters.

Nothing is inferred from the argument (but type inference succeeds) if any of the following are true:

[...]

The argument is a method group.

Thus, the compiler is not able to use the delegate type of Square to infer the type of T2. Note that if you change the declaration to

public static List<T> Map<T>(List<T> inputs, Func<T, T> f) {
        return inputs.ConvertAll((x) => f(x));
}

then

var outputs = Map(inputs, Square);

is legal. In this case, it has already resolved that T is int from the fact that inputs is a List<int>.

Now, the deeper question is why is the above the specification? That is, why don't method groups play a role in type parameter resolution? I think it's because of cases like this:

class Program {
    public static T M<T>(Func<T, T> f) {
        return default(T);
    }

    public static int F(int i) {
        return i;
    }

    public static float F(float f) {
        return f;
    }

    static void Main(string[] args) {
        M(F); // which F am I?
    }
}
Jason
Just for multiple validation, IanG from this MSDN C# Forum seems to say the same thing and goes into a bit of the Why the spec says so: http://social.msdn.microsoft.com/Forums/en/csharplanguage/thread/a4847737-4a6b-4fcd-89f2-1b213aaf8422 I found it somewhat enlightening too.
John K
@jdk: Hmm...see my edit.
Jason
`Map<T>(List<T> inputs, Func<T, T> f)`? Not much of a map function if input and output are the same type, eh? ;)
Juliet
@Juliet: It's `M` for "method" not `Map`. :-)
Jason
@Jason, I think Juliet was commenting on the Map function, rather than on your illustration of why there is no generic type inference on named method groups. @Juliet, it's obviously reasonable to have a function that returns a value of same type as it's input (eq, Square). The point of Map is to apply that function to a list of input, and produce a list of output. That is still a Map function, even if the input and output are of the same type (in this case, int). It may be a special case, but it is still a Map operation.
Cheeso
@Cheeso: You're right; that was a misreading on my part.
Jason