Generic Methods in .Net Cannot Have Their Return Types Inferred. Why

Generic methods in .NET cannot have their return types inferred. Why?

The general principle here is that type information flows only "one way", from the inside to the outside of an expression. The example you give is extremely simple. Suppose we wanted to have type information flow "both ways" when doing type inference on a method R G<A, R>(A a), and consider some of the crazy scenarios that creates:

N(G(5))

Suppose there are ten different overloads of N, each with a different argument type. Should we make ten different inferences for R? If we did, should we somehow pick the "best" one?

double x = b ? G(5) : 123;

What should the return type of G be inferred to be? Int, because the other half of the conditional expression is int? Or double, because ultimately this thing is going to be assigned to double? Now perhaps you begin to see how this goes; if you're going to say that you reason from outside to inside, how far out do you go? There could be many steps along the way. See what happens when we start to combine these:

N(b ? G(5) : 123)

Now what do we do? We have ten overloads of N to choose from. Do we say that R is int? It could be int or any type that int is implicitly convertible to. But of those types, which ones are implicitly convertible to an argument type of N? Do we write ourselves a little prolog program and ask the prolog engine to solve what are all the possible return types that R could be in order to satisfy each of the possible overloads on N, and then somehow pick the best one?

(I'm not kidding; there are languages that essentially do write a little prolog program and then use a logic engine to work out what the types of everything are. F# for example, does way more complex type inference than C# does. Haskell's type system is actually Turing Complete; you can encode arbitrarily complex problems in the type system and ask the compiler to solve them. As we'll see later, the same is true of overload resolution in C# - you cannot encode the Halting Problem in the C# type system like you can in Haskell but you can encode NP-HARD problems into overload resolution problems.) (See below)

This is still a very simple expression. Suppose you had something like

N(N(b ? G(5) * G("hello") : 123));

Now we have to solve this problem multiple times for G, and possibly for N as well, and we have to solve them in combination. We have five overload resolution problems to solve and all of them, to be fair, should be considering both their arguments and their context type. If there are ten possibilities for N then there are potentially a hundred possibilities to consider for N(N(...)) and a thousand for N(N(N(...))) and very quickly you would have us solving problems that easily had billions of possible combinations and made the compiler very slow.

This is why we have the rule that type information only flows one way. It prevents these sorts of chicken and egg problems, where you are trying to both determine the outer type from the inner type, and determine the inner type from the outer type and cause a combinatorial explosion of possibilities.

Notice that type information does flow both ways for lambdas! If you say N(x=>x.Length) then sure enough, we consider all the possible overloads of N that have function or expression types in their arguments and try out all the possible types for x. And sure enough, there are situations in which you can easily make the compiler try out billions of possible combinations to find the unique combination that works. The type inference rules that make it possible to do that for generic methods are exceedingly complex and make even Jon Skeet nervous. This feature makes overload resolution NP-HARD.

Getting type information to flow both ways for lambdas so that generic overload resolution works correctly and efficiently took me about a year. It is such a complex feature that we only wanted to take it on if we absolutely positively would have an amazing return on that investment. Making LINQ work was worth it. But there is no corresponding feature like LINQ that justifies the immense expense of making this work in general.


UPDATE: It turns out that you can encode arbitrarily difficult problems in the C# type system. C# has nominal generic subtyping with generic contravariance, and it has been shown that you can build a Turing Machine out of generic type definitions and force the compiler to execute the machine, possibly going into infinite loops. At the time I wrote this answer the undecidability of such type systems was an open question. See https://stackoverflow.com/a/23968075/88656 for details.

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>();

Why type arguments cannot be inferred for generic types?

You should think about what your actual requirements for the type are.

In your case, what do you want to do? You want to be able to execute action on a client which you create in the method. That client is of the type you pass as the generic type argument. Do you need to know that it’s a ClientBase<something> in order to execute the action? No.

What else do you do on the object? You open and close the channel. Those are actions ensured by ICommunicationObject which ClientBase<T> implements.

That’s all your requirements. So you want to have the following constraints:

  • Being able to create an object of the type.
  • The type implementing ICommunicationObject so you can open/close the channel.

So your method could look like this:

public static void Call<T>(Action<T> action)
where T: ICommunicationObject, new()
{
T serviceProxy = new T();
try
{
action(serviceProxy);
serviceProxy.Close();
}
catch (Exception ex)
{
serviceProxy.Abort();
throw;
}
}

Finally, to answer your question on why the compiler cannot resolve this automatically: If you have generic type arguments, there are two possibilites. Either the compiler is able to infer all type arguments, in which case you can leave them out, or the compiler is not able to infer all arguments in which case you need to specify them all. After all Foo<X>() and Foo<X, Y>() are different method signatures, so if the latter also allowed Foo<X>(), it would be ambiguous.

As for why the compiler cannot infer all type arguments in your case, this is simply because relationships between type arguments given by the constraints are not evaluated for the type inferring.

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);

How does type inference work with overloaded generic methods

Assuming your Add methods are in the class named DataCollector;
you can add extension method which accepts InfoData<ExtraInfo> and returns the same type as shown below.

public static class DataCollectorExtensions
{
public static InfoData<ExtraInfo> AddInfoData(this DataCollector dataCollector, InfoData<ExtraInfo> data)
{
return dataCollector.Add<InfoData<ExtraInfo>, ExtraInfo>(data);
}
}

In this way you have to specify the generic parameters only once inside the extension method and use the extension method elsewhere without need to specify generic parameter.

new DataCollector().AddInfoData(new InfoData<ExtraInfo>());

You still have to name the method other than Add (I have named AddInfoData) otherwise compiler again picks up the public TData Add<TData>(TData data) method in the the DataCollector class.
As you're infact adding InfoData this method name should be acceptable.
I suppose this solution should be acceptable for all practical purposes.

.NET: Inferred generic types on static methods

From your error message:

The type arguments for method '[...].Map<T,T2>(System.Collections.Generic.List<T>, System.Func<T,T2>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Note that the error message says that it can not figure out the type arguments. That is, it is having trouble resolving one of the type parameters T or T2. This is because of §25.6.4 (Inference of type arguments) of the specification. This is the part of the specification the deals with inferring generic type parameters.

Nothing is inferred from the argument (but type inference succeeds) if any of the following are true:

[...]

The argument is a method group.

Thus, the compiler is not able to use the delegate type of Square to infer the type of T2. Note that if you change the declaration to

public static List<T> Map<T>(List<T> inputs, Func<T, T> f) {
return inputs.ConvertAll((x) => f(x));
}

then

var outputs = Map(inputs, Square);

is legal. In this case, it has already resolved that T is int from the fact that inputs is a List<int>.

Now, the deeper question is why is the above the specification? That is, why don't method groups play a role in type parameter resolution? I think it's because of cases like this:

class Program {
public static T M<T>(Func<T, T> f) {
return default(T);
}

public static int F(int i) {
return i;
}

public static float F(float f) {
return f;
}

static void Main(string[] args) {
M(F); // which F am I?
}
}

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.

Why can't nested generic types be inferred?

Method type inference ignores generic constraints on the method type parameters (*). Method type inference reasons only about deductions that can be made by comparing arguments to formal parameter types. Since the only generic type parameter that appears in your formal parameter types is TFoo, there is no way to deduce TBar.

Many people believe this design decision to be wrong, wrong, wrong. Though I take their point, this decision does lead to what are in my opinion some nice properties. For an extended debate on this issue, see the bazillion or so comments on this blog article telling me that I am wrong, wrong, wrong:

http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx


(*) Note that I said constraints on method type parameters are ignored, not constraints in general. If the deduced formal parameter types are constructed generic types such that the construction violates their type parameter constraints then this fact causes type inference to fail and the method is not a candidate for overload resolution. But under no circumstances do we make a deduction from a constraint other than "Hmm, clearly this is not going to work".



Related Topics



Leave a reply



Submit