1

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.

themills22
  • 13
  • 2

1 Answers1

0

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.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Okay, that helps clarify some of my confusion regarding type inference of an anonymous method, though I'm still curious regarding some of the details in the hand-waving. When I was trying to work Situation 1 out myself I was never quite sure how the bounds of `T` was calculated, e.g. was the lower-bound set `{ int, int? }` or `{ int? }` after the first phase or even something else entirely? I was having a hard time figuring out type inference for this anonymous method when I was going through the docs. – themills22 May 08 '23 at 18:20
  • @themills22: If it's any consolation to you, everyone else has a hard time with this as well. I wouldn't like to speculate on the lower-bound set without spending a serious amount of time poring over the spec... this is one of the most complex areas of the language. – Jon Skeet May 08 '23 at 18:47
  • No worries, I was crossing my fingers that you could give a quick-ish response but wasn't expecting one due to the problem's complexity. You've given me some new information to consider when I take another detailed look into the type inferencing. – themills22 May 08 '23 at 20:16