Почему нельзя вывести эти параметры универсального типа?

Учитывая следующие интерфейсы / классы:

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

Почему компилятор не может определить правильные типы, если я попытаюсь написать что-то вроде следующего:

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?

Я просто получаю сообщение «аргументы типа не могут быть выведены». Это ограничение общего вывода типа в целом или есть способ заставить эту работу работать?


person Jon M    schedule 07.06.2010    source источник


Ответы (2)


У вас есть ограничение TRequest : IRequest<TResponse>, но это не означает, что TResponse может быть автоматически выведено из TRequest. Учтите, что классы могут реализовывать несколько интерфейсов, а TRequest может реализовывать несколько IRequest<TResponse> типов; Возможно, вы не делаете этого в своем собственном дизайне, но компилятору было бы довольно сложно пройти через всю иерархию классов, чтобы вывести этот конкретный параметр.

Короче говоря, метод Handle принимает два параметра универсального типа (TRequest и TResponse), а вы даете ему только тот, который он действительно может использовать. Вывод происходит только с аргументами фактического типа, но не с типами, которые они наследуют или реализуют.

person Aaronaught    schedule 07.06.2010
comment
Спасибо, Aaronaught, я не рассматривал несколько реализаций интерфейса, это имеет большой смысл. Однако это заставляет меня думать, что это должно заставить его работать: IRequest<CustomerResponse> request = new GetCustomerByIdRequest {CustomerId = 1234}; но это не так. Разумеется, с явным объявлением интерфейса нет двусмысленности по поводу TResponse? - person Jon M; 07.06.2010
comment
@Jon: Я понимаю, почему вы так думаете, но нет, это тоже не совсем работает. Передача абстрактного IRequest<TResponse> только гарантирует, что ограничение выполняется для конкретного TResponse; это не помогает сделать вывод, каков фактический тип TResponse (который должен быть известен до оценки ограничения). Фактически, я не верю, что компилятор вообще смотрит на ограничения типа при выводе типа; сначала он пытается вывести типы, а затем гарантирует, что выведенные типы соответствуют ограничениям. Вот почему вы не можете перегружать общие методы, которые отличаются только ограничениями. - person Aaronaught; 07.06.2010

Я думаю, это зависит от использования ...

В этом случае что-то (вы не перечислите это выше) вызывает service.Handle (request);

Если потребительский класс не включает универсальный тип в свое собственное объявление, я думаю, вы столкнетесь с этой проблемой.

Например ... (это не сработает)

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

Это должно сработать ... (класс должен знать, что такое TResponse)

public class MyClass<TResponse> where TResponse : YOURTYPE
{
     var service = new HandlingService();
     var request = new GetCustomerByIdRequest { CustomerId = 1234 };
     var response = service.Handle(request);
}
person ctorx    schedule 07.06.2010
comment
Я не понимаю, как это будет работать, ведь TResponse в контексте MyClass<TResponse> не имеет никакого отношения к параметрам предполагаемого типа service.Handle, конечно? - person Jon M; 07.06.2010