Why doesn't C# infer my generic types?

28,290

Solution 1

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.

Solution 2

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 3

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 4

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 5

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.
}
Share:
28,290

Related videos on Youtube

Steven
Author by

Steven

I'm a freelance developer and architect from The Netherlands, working with .NET technology on a daily basis. You can read my weblog or follow me on Twitter. I'm a developer of the Simple Injector open source dependency injection library and coauthor of the book Dependency Injection Principles, Practices, and Patterns.

Updated on December 08, 2021

Comments

  • Steven
    Steven over 2 years

    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<TQuery, 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.
    • Raymond Chen
      Raymond Chen over 12 years
      The compiler doesn't know what TResult to use. At the time it needs to make the decision, it doesn't know that you're going to put it into a string (so maybe it should infer string). And even if it knew, it could also legally be Process<SomeQuery, customClass> where customClass is any class derived from string. See also: Return type inference doesn't work on member groups.
    • Steven
      Steven over 12 years
      @Raymond: Return types have nothing to do with this. See my update. I would expect the compiler TResult as a string, becaue the supplied object implements IQuery<string>.
    • Eric Lippert
      Eric Lippert over 12 years
      @RaymondChen: Thanks for the shout-out Raymond, but that is not the relevant article for this problem. The relevant article is blogs.msdn.com/b/ericlippert/archive/2009/12/10/…
    • trailmax
      trailmax over 9 years
      @Steven Do you remember how you fixed this constraint? did you go with a pile of generic parameters or called the method via reflection?
    • Steven
      Steven over 9 years
      @trailmax: Absolutely. The solution is actually to use some dynamic typing. I wrote this down here. Just take a look at the IQueryProcessor interface and QueryProcessor implementation that I described in that article.
    • trailmax
      trailmax over 9 years
      @Steven oh! I've done pretty much the same, was hoping for a new answer -). And it's great coincidence, because I've already commented on that blog entry!
    • AksharRoop
      AksharRoop over 7 years
      You can workaround this limitation with some trick - joelabrahamsson.com/a-neat-little-type-inference-trick-with-‌​c
  • Steven
    Steven over 12 years
    It's not only the return type that would make type inference succeed. The type implements IQuery<string>.
  • Eric Lippert
    Eric Lippert over 12 years
    Notice also that the first highlighted statement is an error; this line was never updated when we added named and optional arguments, and also is inconsistent with type inference on "params" methods applicable in their expanded form. A future version of the specification will correct the line to take those into account.
  • Steven
    Steven over 12 years
    Thanks for your answer (THE answer). Is it possible for the C# compiler to do constraint inference in the future, or is that out of the question?
  • Brian
    Brian over 12 years
    I'd say that having such inference is out of the question. Eric states in a comment, "This is a deliberate design choice in accordance with the long-standing principles of the design of the C# language."
  • Eric Lippert
    Eric Lippert over 12 years
    @Steven: Brian is correct; we don't have any intention of adding constraints to the list of facts that are used to make inferences.
  • MistyK
    MistyK over 5 years
    you can have p.Process(query, out var result) as of C# 7.0
  • Steven
    Steven almost 4 years
    @EricLippert: considering your update about C# 7.3, I am very interested in reading more on this topic.
  • Ian Kemp
    Ian Kemp almost 4 years
    AFAIK the only thing that changed re type constraints in C# 7.3 was that unmanaged, Delegate, MulticastDelegate, and Enum are now allowed (these were always permitted in the CLR, just not the C# language itself). The only other potential applicable change is that method overload resolution was improved to better disambiguate between multiple candidates.
  • alx9r
    alx9r over 3 years
    FWIW, the quoted section 7.4.2 seems to correspond to 12.6.3.1 of the 5th edition of the spec.
  • Steven
    Steven about 3 years
    Your code compiled 10 years ago. This is because you changed the IQuery<TResult> to an IQuery<TQuery, TResult>. Your change is clever and solves the original problem. But unfortunately, it pushed the burden to the query implementations, which now all have to declare themselves. There's now some unfortunate redundancy. Whether this is acceptable is up to you. I myself rather have some extra complexity inside the query processor implementation, rather than having to complicate my query classes. Still, interesting solution.
  • t.ouvre
    t.ouvre about 3 years
    OK thank you for your explanations. In my situation it is clearly preferable to place the redundancy at the level of the query declaration, because the equivalent of the Process method is called in multiple places, moreover the class representing the result is sometimes itself generic and complex, which makes the calling code difficult to write.