views:

104

answers:

2

Given the following interfaces/classes:

public interface IRequest<TResponse> { }

public interface IHandler<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    TResponse Handle(TRequest request);
}

public class HandlingService
{
    public TResponse Handle<TRequest, TResponse>(TRequest request)
        where TRequest : IRequest<TResponse>
    {
        var handler = container.GetInstance<IHandler<TRequest, TResponse>>();
        return handler.Handle(request);
    }
}

public class CustomerResponse
{
    public Customer Customer { get; set; }
}

public class GetCustomerByIdRequest : IRequest<CustomerResponse>
{
    public int CustomerId { get; set; }
}

Why can't the compiler infer the correct types, if I try and write something like the following:

var service = new HandlingService();
var request = new GetCustomerByIdRequest { CustomerId = 1234 };
var response = service.Handle(request);  // Shouldn't this know that response is going to be CustomerResponse?

I just get the 'type arguments cannot be inferred' message. Is this a limitation with generic type inference in general, or is there a way to make this work?

+4  A: 

You have the constraint TRequest : IRequest<TResponse>, but that doesn't mean that TResponse can be automatically inferred from TRequest. Consider that classes can implement multiple interfaces and TRequest may implement several IRequest<TResponse> types; you may not be doing this in your own design, but it would be pretty complicated for the compiler to have to trudge through the entire class hierarchy to infer that particular parameter.

Long story short, the Handle method takes two generic type parameters (TRequest and TResponse) and you're only giving it one that it can actually use. Inferrence only happens on the actual type arguments, not the types that they inherit or implement.

Aaronaught
Thanks Aaronaught, I hadn't considered multiple interface implementations, makes a lot of sense. However that makes me think that this should make it work: `IRequest<CustomerResponse> request = new GetCustomerByIdRequest {CustomerId = 1234};` but it doesn't. Surely with the explicit interface declaration there's no ambiguity over `TResponse`?
Jon M
@Jon: I can see why you would think that, but no, that doesn't quite work either. Passing an abstract `IRequest<TResponse>` only guarantees that the constraint is satisfied for a specific `TResponse`; it doesn't help to infer what the actual `TResponse` type is (which must be known *before* the constraint is evaluated). In fact, I don't believe that the compiler looks at type constraints at all when doing type inference; it tries to infer the types first, then ensures that the inferred types meet the constraints. That's why you can't overload generic methods that differ only by constraints.
Aaronaught
A: 

I think this depends on the usage...

In this case, something (you don't list it above) is calling service.Handle(request);

If the consuming class does not include the generic type in it's own declaration, I think you will run into this problem.

For example... (this won't work)

public class MyClass
{
     var service = new HandlingService();
     var request = new GetCustomerByIdRequest { CustomerId = 1234 };
     var response = service.Handle(request);
}

This should work... (the class needs to know what TResponse is)

public class MyClass<TResponse> where TResponse : YOURTYPE
{
     var service = new HandlingService();
     var request = new GetCustomerByIdRequest { CustomerId = 1234 };
     var response = service.Handle(request);
}
Matthew
I can't see how this would work, `TResponse` in the context of `MyClass<TResponse>` has no relation to the inferred type parameters of `service.Handle`, surely?
Jon M