У меня много веселья Funcy (забавное предназначение) с помощью общих методов. В большинстве случаев вывод типа С# достаточно умен, чтобы выяснить, какие общие аргументы он должен использовать для моих общих методов, но теперь у меня есть проект, где компилятор С# не преуспевает, хотя я считаю, что ему удастся найти правильные типы.
Может ли кто-нибудь сказать мне, является ли компилятор немного глупым в этом случае, или есть очень ясная причина, почему он не может вывести мои общие аргументы?
Здесь код:
Определения классов и интерфейсов:
interface IQuery<TResult> { }
interface IQueryProcessor
{
TResult Process<TQuery, TResult>(TQuery query)
where TQuery : IQuery<TResult>;
}
class SomeQuery : IQuery<string>
{
}
Некорректный код, который не компилируется:
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);
}
}
Почему это? Что мне здесь не хватает?
Здесь сообщение об ошибке компилятора (это не оставляет нам ничего хорошего):
Аргументы типа для метода IQueryProcessor.Process(TQuery) не могут быть выведены из использования. Попробуйте указать введите аргументы явно.
Я полагаю, что С# должен иметь возможность сделать вывод, что это связано с тем, что:
- Я предоставляю объект, который реализует
IQuery<TResult>
. - Только версия
IQuery<TResult>
, которая реализует тип, имеет значениеIQuery<string>
и, следовательно, TResult должен бытьstring
. - С этой информацией у компилятора есть TResult и TQuery.
Решение
Для меня лучшим решением было изменить интерфейс IQueryProcessor
и использовать динамическую типизацию в реализации:
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);
}
}
Интерфейс IQueryProcessor
теперь принимает параметр IQuery<TResult>
. Таким образом, он может вернуть TResult
, и это решит проблемы с точки зрения потребителя. Нам нужно использовать отражение в реализации, чтобы получить фактическую реализацию, так как нужны конкретные типы запросов (в моем случае). Но здесь идет динамичная печать на помощь, которая будет отражать нас. Подробнее об этом можно прочитать в статье .