Ответ 1
(Я не прошел через версию делегатов. Я решил, что этот ответ уже достаточно длинный...)
Давайте начнем с упрощения кода. Вот короткий, но полный пример, который все еще демонстрирует проблему, но устраняет все неуместное. Я также изменил порядок аргументов типа в IFunction
только для соответствия более нормальным соглашениям (например, Func<T, TResult>
):
// We could even simplify further to only have IElement and IChildElement...
public interface IElement {}
public interface IChildElement : IElement {}
public interface IGrandchildElement : IChildElement {}
public interface IFunction<in T, TResult> where T : IElement
{
TResult Evaluate(T x);
}
public class Foo : IFunction<IChildElement, double>,
IFunction<IGrandchildElement, double>
{
public double Evaluate(IChildElement x) { return 0; }
public double Evaluate(IGrandchildElement x) { return 1; }
}
class Test
{
static TResult Evaluate<TResult>(IFunction<IChildElement, TResult> function)
{
return function.Evaluate(null);
}
static void Main()
{
Foo f = new Foo();
double res1 = Evaluate(f);
double res2 = Evaluate<double>(f);
}
}
У этой проблемы все еще есть та же проблема:
Test.cs(27,23): error CS0411: The type arguments for method
'Test.Evaluate<TResult>(IFunction<IChildElement,TResult>)' cannot be
inferred from the usage. Try specifying the type arguments explicitly.
Теперь о том, почему это происходит... проблема - это вывод типа, как говорили другие. Механизм вывода типа в С# (как на С# 3) довольно хорош, но он не настолько мощный, насколько это возможно.
Посмотрите, что происходит в части вызова метода, со ссылкой на спецификацию языка С# 5.
7.6.5.1 (вызовы метода) - важная часть здесь. Первый шаг:
Создан набор методов-кандидатов для вызова метода. Для каждого метода F, связанного с группой методов M:
- Если F не является общим, F является кандидатом, когда:
- M не имеет списка аргументов типа и
- F применимо относительно A (§7.5.3.1).
- Если F является общим и M не имеет списка аргументов типа, F является кандидатом, когда:
- Вывод типа (§7.5.2) преуспевает, выводя список аргументов типа для вызова и
- Когда аргументы inferred type заменяются соответствующими параметрами типа метода, все построенные типы в списке параметров F удовлетворяют их ограничениям (§4.4.4), а список параметров F применим относительно A (§ 7.5.3.1).
- Если F является общим и M содержит список аргументов типа, F является кандидатом, когда:
- F имеет такое же количество параметров типа метода, как и в списке аргументов типа, и
- После того, как аргументы типа заменяются соответствующими параметрами типа метода, все построенные типы в списке параметров F удовлетворяют их ограничениям (§4.4.4), а список параметров F применим относительно A (§ 7.5.3.1).
Теперь здесь группа методов M
представляет собой набор с единственным методом (Test.Evaluate
) - к счастью, раздел 7.4 (поиск элемента) прост. Поэтому у нас есть только один метод F
.
Это общий, и M не имеет списка аргументов типа, поэтому мы закончим прямо в разделе 7.5.2 - вывод типа. Обратите внимание, что если список аргументов отсутствует, это полностью пропущено, и третья основная отметка выше, поэтому вызов Evaluate<double>(f)
завершается успешно.
Итак, у нас есть довольно хороший признак, что проблема заключается в выводе типа. Пусть погрузится в него. (Это то, где это становится сложно, я боюсь.)
7.5.2 сама по себе в основном просто описание, включая тот факт, что вывод типа происходит поэтапно.
Общий метод, который мы пытаемся вызвать, описывается как:
Tr M<X1...Xn>(T1 x1 ... Tm xm)
и вызов метода описывается как:
M(E1 ... Em)
Итак, в нашем случае мы имеем:
- T r is
TResult
, который совпадает с X 1. - T 1 is
IFunction<IChildElement, TResult>
- x 1 is
function
, параметр значения - E 1 is
F
, который имеет типFoo
Теперь попробуйте применить это для остального вывода типа...
7.5.2.1 Первая фаза
Для каждого из аргументов метода E i:
- Если E i является анонимной функцией, то явный вывод типа параметра (§7.5.2.7) производится из E i в T i
- В противном случае, если E i имеет тип U и x i - это параметр значения, то вывод с нижней границей делается из U в T iсуб > .
- В противном случае, если E i имеет тип U и x i является параметром ref или out, то точный вывод делается из U в T iсуб > .
- В противном случае для этого аргумента не делается никаких указаний.
Здесь важна вторая маркерная точка: E 1 не является анонимной функцией, E 1 имеет тип Foo
и x 1 - параметр значения. Таким образом, мы получаем вывод с нижней границей от Foo
до T 1. Этот вывод с нижней границей описан в 7.5.2.9. Важная часть здесь:
В противном случае устанавливает U 1... U k и V 1... V k являются определяется путем проверки того, применяется ли какой-либо из следующих случаев:
- [...]
- V - построенный класс, структура, интерфейс или делегат типа C < V 1... V k > и существует уникальный тип C < U 1... U k > gt; такой, что U (или, если U является параметром типа, его эффективным базовым классом или любым членом его эффективного набора интерфейсов) идентичен, наследуется от (прямо или косвенно) или реализует (прямо или косвенно) C < U 1суб > ... U <суб > ксуб → . (Ограничение "единственности" означает, что в случае интерфейса C <T> {} класс U: C <X> , C <Y> {}, то не делается никаких выводов при выводе из U в C <T> поскольку U 1 может быть X или Y.)
Для целей этой части U
есть Foo
, а V
- IFunction<IChildElement, TResult>
. Однако Foo
реализует как IFunction<IChildElement, double>
, так и IFunction<IGrandchildelement, double>
. Поэтому, хотя в обоих случаях мы закончили с U 2 как double
, этот пункт не выполняется.
Одна вещь, которая меня удивляет, заключается в том, что она не полагается на контравариантность T
in IFunction<in T, TResult>
. Мы получаем ту же проблему, если удалим часть in
. Я бы ожидал, что он будет работать в этом случае, так как не было бы перехода от IFunction<IGrandchildElement, TResult>
к IFunction<IChildElement, TResult>
. Возможно, эта часть является ошибкой компилятора, но, скорее всего, я неправильно понимаю спецификацию. Однако в случае, если это действительно дано, это не имеет значения - из-за контравариантности T
существует такое преобразование, поэтому оба интерфейса действительно значительны.
В любом случае, это означает, что на самом деле мы не получаем вывода какого-либо типа из этого аргумента!
Это вся первая фаза.
Вторая фаза описывается следующим образом:
7.5.2.2 Вторая фаза
Вторая фаза выполняется следующим образом:
- Все незафиксированные переменные типа Xi, не зависящие от (п. 7.5.5.5), фиксированы (X7.2.2.10).
- Если таких переменных типа не существует, все незафиксированные переменные типа Xi фиксированы, для которых выполняются все следующие условия:
- Существует хотя бы одна переменная типа Xj, которая зависит от Xi
- Xi имеет непустое множество оценок
- Если такие переменные типа не существуют и существуют все еще нефиксированные переменные типа, запрос типа терпит неудачу.
- В противном случае, если никаких дополнительных переменных нефиксированного типа не существует, вывод типа завершается успешно.
- В противном случае для всех аргументов Ei с соответствующим типом параметра Ti, где выходные типы (§7.5.2.4) содержат незафиксированные переменные типа Xj, но входные типы (§7.5.2.3) не являются выходным типом вывода (§7.5. 2.6) производится из Ei в Ti. Затем повторяется вторая фаза.
Я не буду копировать все подпункты, но в нашем случае...
- Переменная типа X 1 не зависит от каких-либо других переменных типа, поскольку нет никаких других переменных типа. Поэтому нам нужно исправить X 1. (Ссылка здесь неверна - на самом деле это должно быть 7.5.2.11. Я позволю Мадзу знать.)
У нас нет границ для X 1 (потому что предыдущий выше вывод не помог), поэтому мы заканчиваем неудачный вывод типа в этой точке. Взрыва. Все это зависит от части уникальности в 7.5.2.9.
Конечно, это можно исправить. Часть вывода типа спецификации может быть сделана более мощной - проблема в том, что это также усложнило бы ее, в результате чего:
- Разработчикам сложнее рассуждать о типе вывода (это достаточно сложно, как есть!)
- Трудно правильно указать без пробелов.
- Сложнее реализовать правильно
- Вполне возможно, что он имеет худшую производительность во время компиляции (что может быть проблемой в интерактивных редакторах, таких как Visual Studio, которым необходимо выполнить вывод того же типа для таких вещей, как Intellisense).
Это все балансирующий акт. Я думаю, что команда С# сделала очень хорошо - тот факт, что он не работает в угловых случаях, например, это не слишком большая проблема, IMO.