Why doesn't C# infer my generic types?

C#.NetC# 4.0Type Inference

C# Problem Overview


I'm having lots of Funcy fun (fun intended) with generic methods. In most cases C# type inference is smart enough to find out what generic arguments it must use on my generic methods, but now I've got a design where the C# compiler doesn't succeed, while I believe it could have succeeded in finding the correct types.

Can anyone tell me whether the compiler is a bit dumb in this case, or is there a very clear reason why it can't infer my generic arguments?

Here's the code:

Classes and interface definitions:

interface IQuery<TResult> { }

interface IQueryProcessor
{
    TResult Process<TQuery, TResult>(TQuery query)
        where TQuery : IQuery<TResult>;
}

class SomeQuery : IQuery<string>
{
}

Some code that does not compile:

class Test
{
    void Test(IQueryProcessor p)
    {
        var query = new SomeQuery();

        // Does not compile :-(
        p.Process(query);

        // Must explicitly write all arguments
        p.Process<SomeQuery, string>(query);
    }
}

Why is this? What am I missing here?

Here's the compiler error message (it doesn't leave much to our imagination):

> The type arguments for method IQueryProcessor.Process TResult>(TQuery) cannot be inferred from the usage. Try specifying the > type arguments explicitly.

The reason I believe C# should be able to infer it is because of the following:

  1. I supply an object that implements IQuery<TResult>.
  2. That only IQuery<TResult> version that type implements is IQuery<string> and thus TResult must be string.
  3. With this information the compiler has TResult and TQuery.

C# Solutions


Solution 1 - C#

A bunch of people have pointed out that C# does not make inferences based on constraints. That is correct, and relevant to the question. Inferences are made by examining arguments and their corresponding formal parameter types and that is the only source of inference information.

A bunch of people have then linked to this article:

https://docs.microsoft.com/en-us/archive/blogs/ericlippert/c-3-0-return-type-inference-does-not-work-on-method-groups

That article is both out-of-date and irrelevant to the question. It is out-of-date because it describes a design decision we made in C# 3.0 which we then reversed in C# 4.0, mostly based on the response to that article. I've just added an update to that effect to the article.

It is irrelevant because the article is about return type inference from method group arguments to generic delegate formal parameters. That is not the situation the original poster asks about.

The relevant article of mine to read is rather this one:

https://docs.microsoft.com/en-us/archive/blogs/ericlippert/constraints-are-not-part-of-the-signature

UPDATE: I have heard news that C# 7.3 has slightly changed the rules for when constraints are applied, making the above ten-year-old article no longer accurate. When I have time I'll review the changes my former colleagues have made and see if it is worthwhile to post a correction on my new blog; until then, use caution and see what C# 7.3 does in practice.

Solution 2 - C#

C# will not infer generic types based on the return type of a generic method, only the arguments to the method.

It also doesn't use the constraints as part of the type inference, which eliminates the generic constraint from supplying the type for you.

For details, see Eric Lippert's post on the subject.

Solution 3 - C#

It doesn't use constraints to infer types. Rather it infers types (when possible) and then checks constraints.

Therefore, while the only possible TResult that could be used with a SomeQuery parameter, it won't see this.

Note also, that it would be perfectly possible for SomeQuery to also implement IQuery<int>, which is one reason why this is limitation on the compiler may not be a bad idea.

Solution 4 - C#

I won't go into the why again, I have no illusions of being able to do a better explanation than Eric Lippert.

However, there is a solution that doesn't require late binding or extra parameters to your method call. It's not super intuitive however, so I'll leave it to the reader to decide if it's an improvement.

First off, modify IQuery to make it self-referencing:

public interface IQuery<TQuery, TResult> where TQuery: IQuery<TQuery, TResult>
{
}

Your IQueryProcessor would look like this:

public interface IQueryProcessor
{
    Task<TResult> ProcessAsync<TQuery, TResult>(IQuery<TQuery, TResult> query)
        where TQuery: IQuery<TQuery, TResult>;
}

An actual query type:

public class MyQuery: IQuery<MyQuery, MyResult>
{
    // Neccessary query parameters
}

An implementation of the processor might look like:

public Task<TResult> ProcessAsync<TQuery, TResult>(IQuery<TQuery, TResult> query)
    where TQuery: IQuery<TQuery, TResult>
{
    var handler = serviceProvider.Resolve<QueryHandler<TQuery, TResult>>();
    // etc.
}

Solution 5 - C#

The spec lays this out pretty clearly:

>Section 7.4.2 Type Inference

>If the supplied number of arguments is different than the number of parameters in the method, then inference immediately fails. Otherwise, assume that the generic method has the following signature:

>Tr M(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(E1…Em)becomes valid.

As you can see, the return type is not used for type inference. If the method call does not map directly to the type arguments inference immediately fails.

The compiler does not just assume that you wanted string as the TResult argument, nor can it. Imagine a TResult derived from string. Both would be valid, so which to choose? Better to be explicit.

Solution 6 - C#

The why has been well answered but there is an alternative solution. I face the same issues regularly however dynamic or any solution using reflection or allocating data is out of question in my case (joy of video games...)

So instead I pass the return as an out parameters which is then correctly inferred.

interface IQueryProcessor
{
     void Process<TQuery, TResult>(TQuery query, out TResult result)
         where TQuery : IQuery<TResult>;
}

class Test
{
    void Test(IQueryProcessor p)
    {
        var query = new SomeQuery();

        // Instead of
        // string result = p.Process<SomeQuery, string>(query);

        // You write
        string result;
        p.Process(query, out result);
    }
}

The only drawback I can think of is that it's prohibiting usage of 'var'.

Solution 7 - C#

Another workaround for this issue is to add additional parameter for type resolution.To avoid changes in existing codebase such parameter can be added to extension method. For instance you can add the following extension method:

static class QueryProcessorExtension
{
    public static TResult Process<TQuery, TResult>(
        this IQueryProcessor processor, TQuery query,
        //Additional parameter for TQuery -> IQuery<TResult> type resolution:
        Func<TQuery, IQuery<TResult>> typeResolver)
        where TQuery : IQuery<TResult>
    {
        return processor.Process<TQuery, TResult>(query);
    }
}

Now we can use this extension as follows:

void Test(IQueryProcessor p)
{
    var query = new SomeQuery();

    //You can now call it like this:
    p.Process(query, x => x);
    //Instead of
    p.Process<SomeQuery, string>(query);
}

Which is far from ideal but much better than providing types explicitly.

P.S. Related links for this issue in dotnet repository:

https://github.com/dotnet/csharplang/issues/997

https://github.com/dotnet/roslyn/pull/7850

Solution 8 - C#

I know it's a very old thread, and I don't know if the following snippet was valid ten years ago (out var surely not), but today it is compiling :

interface IQuery<TQuery, TResult>
{
}

interface IQueryProcessor
{
    void Process<TQuery, TResult>(IQuery<TQuery, TResult> query, out TResult result)
        where TQuery : IQuery<TQuery, TResult>;
    TResult ProcessAndReturn<TQuery, TResult>(IQuery<TQuery, TResult> query)
        where TQuery : IQuery<TQuery, TResult>;
}

class SampleQueryResult
{
}

class SampleQuery : IQuery<SampleQuery, SampleQueryResult>
{
}

class Program
{
    static void Main(string[] args)
    {
        IQueryProcessor qp = null; // get it from di ?

        qp.Process(new SampleQuery(), out var r1);
        var r2 = qp.ProcessAndReturn(new SampleQuery());

        SampleQueryResult r;
        r = r1;
        r = r2;
    }
}

So today, there is no need to explicitly set the TResult type to use the Process method.

Solution 9 - C#

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionStevenView Question on Stackoverflow
Solution 1 - C#Eric LippertView Answer on Stackoverflow
Solution 2 - C#Reed CopseyView Answer on Stackoverflow
Solution 3 - C#Jon HannaView Answer on Stackoverflow
Solution 4 - C#ThorarinView Answer on Stackoverflow
Solution 5 - C#Ed S.View Answer on Stackoverflow
Solution 6 - C#Baptiste DupyView Answer on Stackoverflow
Solution 7 - C#Roman ArtiukhinView Answer on Stackoverflow
Solution 8 - C#t.ouvreView Answer on Stackoverflow
Solution 9 - C#gunr2171View Answer on Stackoverflow