Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does method type inference fail to infer a type parameter?

I am not exactly sure how to make this question readable/understandable, but hear my out and I hope you will understand my issue when we get to the end (at the very least, it is easily reproduceable).

I try to call a method used for validating results in UnitTests. It has the following signature:

void AssertPropertyValues<TEnumerable, TElement, TProperty>(
  TEnumerable enumerable, 
  Func<TElement, TProperty> propertyPointer, 
  params TProperty[] expectedValues) 
  where TEnumerable : System.Collections.Generic.IList<TElement>

What this means is, that it takes the following input

  1. Any object that is enumerable, and contains objects of the same Type as the intput for 2).
  2. A Func (usually encapsulating lambda expressions) that takes an object of the same Type as the "contents" of 1) and returns an object of the same Type as the Type of the contents of the array provided in 3).
  3. An array of objects of the same Type as that of the output of the Func in 2).

So, an actual execution of this method could look like this:

AssertPropertyValues(
  item.ItemGroups, 
  itemGroup => itemGroup.Name, 
  "Name1", "Name2", "Name3");

At least, that is how I would like it to look like, but I run into the well known compiler error: "The type arguments for the method 'X' cannot be inferred from the usage.", and that is what I do not understand. It should have all the info needed as far as I can see, or perhaps it is another version of the "Covariance and Contravariance" problem?

So for now I am forced to do it like this instead:

AssertPropertyValues(
  item.ItemGroups, 
  (ItemGroup itemGroup) => itemGroup.Name, 
  "Name1", "Name2", "Name3");

Can anyone point out why this scenario can not be inferred by the compiler?

like image 869
Johny Skovdal Avatar asked Jul 16 '13 13:07

Johny Skovdal


1 Answers

Your problem is caused by the fact that constraints are not considered part of the signature and are never used to make deductions during type inference. You are expecting the inference to go:

  • TEnumerable is determined by taking the type of the first argument.
  • TElement is determined by taking the IList<T> implementation information from TElement
  • TProperty is determined by the type of the body of the lambda

But C# never makes that second step because that requires considering information from a constraint. As you note, if you provide that information in the lambda then the compiler makes the deduction based on the formal parameter type.

Fortunately your constraint is completely unnecessary. Rewrite your method to have a simpler signature that doesn't have a constraint:

void AssertPropertyValues<TElement, TProperty>(
  IList<TElement> sequence, 
  Func<TElement, TProperty> projection, 
  params TProperty[] expectedValues)

and now you should be fine.

And while you're at it, you should probably simplify that to IEnumerable<TElement> unless you need an IList<T> for some reason.

like image 161
Eric Lippert Avatar answered Nov 19 '22 09:11

Eric Lippert