Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET: Inferred generic types on static methods

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 ?

like image 820
Cheeso Avatar asked Jan 21 '10 04:01

Cheeso


2 Answers

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?
    }
}
like image 77
jason Avatar answered Oct 19 '22 03:10

jason


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?

like image 2
Dathan Avatar answered Oct 19 '22 03:10

Dathan