Search code examples
c#genericstype-inferenceanonymous-methods

Why is anonymous method that has expression of type int having its type inferred as Func<int?>?


I have a generic function that is supposed to traverse a sequence of properties within a LINQ expression to compare against another passed-in value but discovered that the LINQ expression contained a convert operation, which my function was not expecting. I've been going over the C# type inference docs to see how this could have happened but haven't been having a lot of luck figuring it out. In the process I ended up simplifying the problem to the following.

The generic function:

public static void M<T>(Func<T> func, T value)
{
}

In this block of code the type of T is inferred to be int?, which I wasn't expecting and was trying to figure out why.

int? value = null;
M(() => 5, value);

I was comparing it against this next block of code which fails type inference.

int? value = null;
Func<int> function = () => 5;
M(function, value);

I believe I understand why type inference fails here: the return type of the function no longer needs to be inferred and Func<int> has no implicit conversion to Func<int?> since they are dealing with value types, not reference types. In the first example it seems to me that type inference sees that the anonymous method's expression type of int is implicity convertible to int? and then infers the anonymous method's type to be Func<int?>. Is this what is happening or is something else going on here? If so, I'm kind-of surprised to see type inference reach into the expression and implicitly convert what it was returning.


Solution

  • You're basically thinking along the right lines, although I wouldn't phrase it quite as you did in the question. Type inference is massively complicated, and just reading through the C# standard I'm fairly convinced I've spotted at least one error (xᵢ should be pᵢ in some places; I'll try to get that fixed next week), so I'll avoid the formal process.

    Instead, let's look at the two situations separately:

    Situation 1

    public static void M<T>(Func<T> func, T value) {}
    ...
    int? value = null;
    M(() => 5, value);
    

    There are two arguments: an anonymous function, and a simple variable of type int?.

    The inference process will infer:

    • There must be an implicit conversion from 5 to T, in order for there to be a conversion from the anonymous function to Func<T>
    • There must be an implicit conversion from int? (the type of the value variable) to T, in order for the second argument to be valid

    Massively hand-wavy, but it's reasonable to imagine the compiler saying, "Hey, let's just try T=int? here":

    • The first condition is met, as there is an implicit conversion from 5 to int?. So we can convert () => 5 to a Func<int?>
    • The second condition is trivially met as there's an identity conversion from int? to int?

    Situation 2

    public static void M<T>(Func<T> func, T value) {}
    ...
    int? value = null;
    Func<int> function = () => 5;
    M(function, value);
    

    Here, type inference requires:

    • There must be an implicit conversion from Func<int> to Func<T> for the first argument to be valid - which means there must be an identity conversion from int to T
    • There must be an implicit conversion from int? to T for the second argument to be valid

    Type inference fails as there's no type T that satisfies both of those constraints.