Ответ 1
Он не может вывести тип, потому что тип здесь не определен.
Fun2
не является Func<string, string>
, но может быть назначено Func<string, string>
.
Итак, если вы используете:
public Test()
{
Func<string, string> del = Fun2;
Fun(del);
}
Или:
public Test()
{
Fun((Func<string, string>)Fun2);
}
Затем вы явно создаете Func<string, string>
из Fun2
, и общий тип-вывод работает соответственно.
И наоборот, когда вы делаете:
public Test()
{
Fun<string>(Fun2);
}
Тогда набор перегрузок Fun<string>
содержит только один, который принимает Func<string, string>
, и компилятор может сделать вывод, что вы хотите использовать Fun2
как таковой.
Но вы просите его вывести как общий тип, основанный на типе аргумента, так и тип аргумента, основанного на родовом типе. Это более важный вопрос, чем любой из видов вывода, который он может сделать.
(Стоит учесть, что в .NET 1.0 не только были делегаты не общие, поэтому вам пришлось бы определять delgate string MyDelegate(string test)
-but, но также было необходимо создать объект с помощью конструктора Fun(new MyDelegate(Fun2))
. Синтаксис изменился использовать делегатов проще несколькими способами, но неявное использование Fun2
as Func<string, string>
по-прежнему является конструкцией объекта-делегата за кадром).
Тогда почему это работает?
class Test
{
public void Fun<T1, T2>(T1 a, Func<T1, T2> f)
{
}
public string Fun2(int test)
{
return test.ToString();
}
public Test()
{
Fun(0, Fun2);
}
}
Потому что тогда он может вывести, чтобы:
-
T1
-int
. -
Fun2
присваиваетсяFunc<int, T2>
для некоторогоT2
. -
Fun2
может быть назначенFunc<int, T2>
, еслиT2
-string
. ПоэтомуT2
является строкой.
В частности, возвращаемый тип Func
может быть выведен из функции после того, как у вас есть типы аргументов. Это так же хорошо (и стоит усилий со стороны компилятора), потому что это важно в Linq Select
. Это вызывает связанный случай, тот факт, что с помощью всего x.Select(i => i.ToString())
нам не хватает информации, чтобы узнать, к чему прикасается лямбда. Как только мы узнаем, что x
IEnumerable<T>
или IQueryable<T>
, мы знаем, что у нас либо есть Func<T, ?>
, либо Expression<Func<T, ?>>
, а остальное можно сделать здесь.
Здесь также стоит отметить, что вывод возвращаемого типа не зависит от двусмысленности, которая выводит другие типы. Подумайте, были ли у нас оба Fun2
(тот, который принимает string
и тот, который принимает int
) в том же классе. Это допустимая перегрузка С#, но делает вывод о типе Func<T, string>
, который Fun2
можно отличить до невозможности; оба действительны.
Однако, в то время как .NET разрешает перегрузку по типу возврата, С# этого не делает. Таким образом, никакая действительная программа С# не может быть неоднозначной для возвращаемого типа Func<T, TResult>
, созданного методом (или лямбда) после определения типа T
. Эта относительная легкость, в сочетании с большой полезностью, делает ее компилятором для нас.