Why Doesn't C# Infer My Generic Types

Why doesn't C# infer my generic types?

This was originally posted in the question and moved here on the OP's behalf.

For me the best solution was to change the IQueryProcessor interface and use dynamic typing in the implementation:

public interface IQueryProcessor
{
TResult Process<TResult>(IQuery<TResult> query);
}

// Implementation
sealed class QueryProcessor : IQueryProcessor {
private readonly Container container;

public QueryProcessor(Container container) {
this.container = container;
}

public TResult Process<TResult>(IQuery<TResult> query) {
var handlerType =
typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = container.GetInstance(handlerType);
return handler.Handle((dynamic)query);
}
}

The IQueryProcessor interface now takes in a IQuery<TResult> parameter. This way it can return a TResult and this will solve the problems from the consumer's perspective. We need to use reflection in the implementation to get the actual implementation, since the concrete query types are needed (in my case). But here comes dynamic typing to the rescue which will do the reflection for us. You can read more about this in this article.

sometimes generic type infer not work. why this happen?

So firstly your method signature only has 1 generic argument it needs two, so it should look like this

public static TCollection ForEachAndReturn<TCollection, TItem>(this TCollection Container, Action<TItem> action)
where TCollection : IEnumerable<TItem>
{
foreach (var item in Container)
{
action(item);
}

return Container;
}

Now you will still get the compiler warning you mentioned. This is because it can infer the type of your collection. However you're generic constraint saying that TCollection must be of type IEnumerable<TSource> only works in giving information about TCollection. That is why you need to tell the compiler the type of TSource. Generic constraints are just that, they are used to constrain, you can't infer the type of the constraint from the thing you are constraining, I imagine that would cause problems potentially. Hence why TSource can not be inferred from the generic argument that is provided by TCollection.

I hope that makes sense.

Edit: Missed a bit sorry, So to get rid of the compiler warning that you are getting you just need to provide the type information.

var myList = new List<string>();

list.ForEachAndReturn<List<string>,string>(i => //do stuff);

Why doesn't the C# compiler consider this generic type inference ambiguous?


What rules are applied to select one of its two generic methods?

The rules in the specification - which are extremely complex, unfortunately. In the ECMA C# 5 standard, the relevant bit starts at section 12.6.4.3 ("better function member").

However, in this case it's relatively simple. Both methods are applicable, with type inference occurring separately for each method:

  • For method 1, TEnum is inferred to be List<MyEnum>
  • For method 2, TEnum is inferred to be MyEnum

Next the compiler starts checking the conversions from arguments to parameters, to see whether one conversion is "better" than the other. That goes into section 12.6.4.4 ("better conversion from expression").

At this point we're considering these conversions:

  • Overload 1: List<MyEnum> to List<MyEnum> (as TEnum is inferred to be List<MyEnum>)
  • Overload 2: List<MyEnum> to IEnumerable<MyEnum> (as TEnum is inferred to be MyEnum)

Fortunately, the very first rule helps us here:

Given an implicit conversion C1 that converts from an expression E to a type T1, and an implicit conversion C2 that converts from an expression E to a type T2, C1 is a better conversion than C2 if at least one of the following holds:

  • E has a type S and an identity conversion exists from S to T1 but not from S to T2

There is an identity conversion from List<MyEnum> to List<MyEnum>, but there isn't an identity conversion from List<MyEnum> to IEnumerable<MyEnum>, therefore the first conversion is better.

There aren't any other conversions to consider, therefore overload 1 is seen as the better function member.

Your argument about "more general" vs "more specific" parameters would be valid if this earlier phase had ended in a tie-break, but it doesn't: "better conversion" for arguments to parameters is considered before "more specific parameters".

In general, both overload resolution is incredibly complicated. It has to take into account inheritance, generics, type-less arguments (e.g. the null literal, the default literal, anonymous functions), parameter arrays, all the possible conversions. Almost any time a new feature is added to C#, it affects overload resolution :(

C# generic method type argument not inferred from usage




Does the type inference give up as soon as there are more than just one suitable candidate?

Yes, in this case it does. While attempting to infer the method's generic type parameter (TResult), the type inference algorithm appears to fail on CountVisitor having two inferences to the type IVisitor<TResult, TVisitable>.


From the C# 5 specification (the most recent I could find), §7.5.2:

Tr M<X1…Xn>(T1 x1 … Tm xm)

With a method call of the form M(E1 …Em) the task of type inference is to find unique type arguments
S1…Sn for each of the type parameters X1…Xn so that the call M<S1…Sn>(E1…Em) becomes valid.

The very first step the compiler takes is as follows (§7.5.2.1):

For each of the method arguments Ei:

  • If Ei is an anonymous function, an explicit parameter type inference (§7.5.2.7) is made from Ei
    to Ti

  • Otherwise, if Ei has a type U and xi is a value parameter then a lower-bound inference is made from U to Ti.

You only have one argument, so we have that the only Ei is the expression new CountVisitor(). It's clearly not an anonymous function, so we're in the second bullet point. It's trivial to see that in our case, U is of type CountVisitor. The "xi is a value parameter" bit basically means it's not an out, in, ref etc. variable, which is the case here.

At this point, we now need to make a lower-bound inference from CountVisitor to IVisitor<TResult, TVisitable> The relevant part of §7.5.2.9 (where due to a variable switch, we have V = IVisitor<TResult, TVisitable> in our case):

  • Otherwise, sets U1…Uk and V1…Vk are determined by checking if any of the following cases apply:

    • V is an array type V1[…] and U is an array type U1[…] (or a type parameter whose effective base type is U1[…]) of the same rank
    • V is one of IEnumerable<V1>, ICollection<V1> or IList<V1> and U is a one-dimensional array type U1[] (or a type parameter whose effective base type is U1[])
    • V is a constructed class, struct, interface or delegate type C<V1…Vk> and there is a unique type C<U1…Uk> such that U (or, if U is a type parameter, its effective base class or any member of its effective interface set) is identical to, inherits from (directly or indirectly), or implements (directly or indirectly) C<U1…Uk>.

(The “uniqueness” restriction means that in the case interface C<T>{} class U: C<X>, C<Y>{}, then no inference is made when inferring from U to C<T> because U1 could be X or Y.)

We can skip past the first two cases as they're clearly not applicable, the third case is the one we fall into. The compiler attempts to find a unique type C<U1…Uk> that CountVisitor implements and finds two such types, IVisitor<int, Foo> and IVisitor<int, Bar>. Note that the example the spec gives is nearly identical your example.

Because of the uniqueness constraint, no inference is made for this method argument. With the compiler not able to infer any type information from the argument, it has nothing to go on to try to infer TResult and thus fails.


As to why there exists a uniqueness constraint, my guess is that it simplifies the algorithm and thus compiler implementation. If you're interested, here's a link to source code where Roslyn (modern C# compiler) implements generic method type inference.

Why can't the compiler infer a generic parameter when there is a generic return value?

It's not a matter of compiler - it's that C# requires that you either explicitly specify all the type arguments or let it infer all of them.

There is no middle ground using the syntax you've attempted, and I imagine it's because if you had a generic method like this:

public void DoSomething<T1, T2>(T1 data, T2 data)
{
// ...
}

And you used it like this:

var obj1 = "Hello!";
var obj2 = "Hello?";
DoSomething<IEnumerable<char>>(obj1, obj2);

The last line could be shorthand for two equally valid things:

DoSomething<string, IEnumerable<char>>(obj1, obj2);
DoSomething<IEnumerable<char>, string>(obj1, obj2);

A different syntax (like <string, ?>) or additional inference rules would have to be put in place to make cases like this meaningful and non-ambiguous. I imagine the design team thought that it wasn't worth it.


Note that if you really want partial generic type inference, there is a common pattern of splitting the call into two calls, with a helper object to hold the information between the calls. This is essentially currying, applied to type parameters.

I'll present the pattern in a form that uses a public interface and an private implementation but if you're not concerned about that, you can skip the interface altogether.

public TResult DoSomething<TResult, TSource>(TSource data)
{
// ...
}

Would become:

public IWrapper<TSource> DoSomething<TSource>(TSource data)
{
return new WrapperImplementation<TSource>(data);
}

Where:

public interface IWrapper<T>
{
TResult Apply<TResult>();
}

class WrapperImplementation<T> : IWrapper<T>
{
private readonly T _source;

public WrapperImplementation(T source)
{
_source = source;
}

public TResult Apply<TResult>()
{
// ...
}
}

The usage is:

DoSomething("Hello").Apply<int>();

Infer generic type with two generic type parameters

Because the C# rules don't allow that.

It would be feasible for C# to have a rule where if some of the types related to parameters (and hence could be inferred at least some of the time) and the number of explicitly given types was the same as the number of remaining un-inferrable types, then the two would work in tandem.

This would need someone to propose it, convince other people involved in the decision-making around C# it was a good idea, and then for it to be implemented. This has not happened.

Aside from the fact that features start off having to prove themselves worth the extra complexity they bring (add anything to a language and it is immediately more complex with more work, more chance of compiler bugs etc.) the question then is, is it a good idea?

On the plus, your code in this particular example would be better.

On the negative, everyone's code is now slightly more complicated, because there's more ways something that is wrong can be wrong, resulting either in code that fails at runtime rather than compile-time or less useful error messages.

That people already find some cases around inference to be confusing would point to the idea that adding another complicating case would not be helpful.

Which isn't to say that it is definitely a bad idea, just that there are pros and cons that make it a matter of opinion, rather than an obvious lack.

Why doesn't C# support implied generic types on class constructors?

Actually, your question isn't bad. I've been toying with a generic programming language for last few years and although I've never come around to actually develop it (and probably never will), I've thought a lot about generic type inference and one of my top priorities has always been to allow the construction of classes without having to specify the generic type.

C# simply lacks the set of rules to make this possible. I think the developers never saw the neccesity to include this. Actually, the following code would be very near to your proposition and solve the problem. All C# needs is an added syntax support.

class Foo<T> {
public Foo(T x) { … }
}

// Notice: non-generic class overload. Possible in C#!
class Foo {
public static Foo<T> ctor<T>(T x) { return new Foo<T>(x); }
}

var x = Foo.ctor(42);

Since this code actually works, we've shown that the problem is not one of semantics but simply one of lacking support. I guess I have to take back my previous posting. ;-)



Related Topics



Leave a reply



Submit