Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiler can't figure out generic types when passing a method

I'm having trouble with C# and generic type inference. I want to write a method that gets passed a method having any type, but the compiler is not able to infere the types of the method I'm passing in. The compiler always complains with with the message

Expected a method with '??? TestFunc(???, ???)' signature

Here's a testcase.

using System;

public class Example
{
    private interface ITest
    {
        int TestFunc(string str, int i);
    }

    private class Test : ITest
    {
        public int TestFunc(string str, int i) { return 0; }
    }

    public static void Main()
    {
        ITest t = new Test();
        DoWork(t.TestFunc);
    }

    public static void DoWork<T1, T2, TResult>(Func<T1, T2, TResult> func)
    {
    }
}

Can anyone explain me what's the problem?

like image 423
Martin Avatar asked Apr 08 '11 09:04

Martin


People also ask

How does a generic method differ from a generic type?

From the point of view of reflection, the difference between a generic type and an ordinary type is that a generic type has associated with it a set of type parameters (if it is a generic type definition) or type arguments (if it is a constructed type). A generic method differs from an ordinary method in the same way.

What are generic methods?

What are generic methods? Explanation: Generic methods are methods that introduce their own type parameters. This is similar to declaring a generic type, but the type parameter scope is limited to the method where it is declared. Static and non-static generic methods are allowed, as well as generic class constructors.


2 Answers

The rules for type inference are hideously complicated, unfortunately. At least, I find them complicated, and I believe Eric and Mads are having another go at simplifying them for the next version of the spec - quite possibly not changing what's implemented, but changing how that's expressed in the spec.

In this case, the fundamental problem is that the parameter types of method groups don't contribute to type inference, even though the return type does. In particular, from 7.5.2.6 of the C# 4 spec:

Otherwise, if E is a method group and T is a delegate type or expression tree type with parameter types T1…Tk and return type Tb, and overload resolution of E with the types T1…Tk yields a single method with return type U, then a lower-bound inference is made from U to Tb.

That deals with return types, but doesn't specify anything about parameter types. The only relevant bit of the spec I can find about parameter types for method groups is this:

If E is a method group or implicitly typed anonymous function and T is a delegate type or expression tree type then all the parameter types of T are input types of E with type T.

That doesn't help to fix any bounds, unfortunately.

So basically, this works:

using System;

public class Example
{
    private interface ITest
    {
        int TestFunc();
        int TestFunc2(string value);
    }

    public static void Main()
    {
        ITest t = null;
        DoWork(t.TestFunc);
        DoWork2(t.TestFunc2);
    }

    public static void DoWork<TResult>(Func<TResult> func)
    {
    }

    public static void DoWork2<TResult>(Func<string, TResult> func)
    {
    }
}

... because the only type parameter that needs to be inferred in either case is the return type. It's when you try to infer type parameters based on the input parameters of the method that things go wrong :(

like image 125
Jon Skeet Avatar answered Oct 23 '22 20:10

Jon Skeet


I assume the compiler does not try to infer the type in this case, since if you have overloads of TestFunc, the desired behavior is not well defined. Consider:

    private class Test 
    {
        public int TestFunc(string str, int i) { return 0; }
        public int TestFunc(string str, long i) { return 0; }
    }

    public static void Main()
    {
        Test t = new Test();
        DoWork(t.TestFunc);
    }

    public static void DoWork<T1, T2, TResult>(Func<T1, T2, TResult> func)
    {
    }
like image 22
Jens Avatar answered Oct 23 '22 20:10

Jens