.NET: выведенные общие типы для статических методов
Предположим, что
public static List<T2> Map<T,T2>(List<T> inputs, Func<T, T2> f)
{
return inputs.ConvertAll((x) => f(x));
}
private int Square(int x) { return x*x; }
public void Run()
{
var inputs = new List<Int32>(new int[]{2,4,8,16,32,64,128,256,512,1024,2048});
// this does not compile
var outputs = Map(inputs, Square);
// this is fine
var outputs2 = Map<Int32,Int32>(inputs, Square);
// this is also fine (thanks, Jason)
var outputs2 = Map<Int32,Int32>(inputs, (x)=>x*x);
// also fine
var outputs2 = Map(inputs, (x)=>x*x);
}
Почему он не компилируется?
EDIT: ошибка:
ошибка CS0411: аргументы типа для метода 'Namespace.Map < T, T2 > (System.Collections.Generic.List <T> , System.Func < T, T2 > )' не могут быть выведены из использования. Попробуйте явно указать аргументы типа.
Почему мне нужно указать тип функции Map()? Не может ли это сделать из пройденного Func<T>
? (в моем случае, квадрат)
Является ли ответ таким же, как и для
Вывод общего типа С# 3.0 - передача делегата в качестве параметра функции?
Ответы
Ответ 1
Из вашего сообщения об ошибке:
Аргументы типа для метода '[...].Map<T,T2>(System.Collections.Generic.List<T>, System.Func<T,T2>)
' не могут быть выведены из использования. Попробуйте явно указать аргументы типа.
Обратите внимание, что в сообщении об ошибке указано, что он не может определить аргументы типа. То есть возникает проблема с разрешением одного из параметров типа T
или T2
. Это из-за §25.6.4 (вывод аргументов типа) спецификации. Это часть спецификации, заключающаяся в выводе параметров типового типа.
Из аргумента ничего не выводится (но вывод типа преуспевает), если выполнено одно из следующих утверждений:
[...]
Аргумент - это группа методов.
Таким образом, компилятор не может использовать тип делегата Square
для вывода типа T2
. Обратите внимание, что если вы измените объявление на
public static List<T> Map<T>(List<T> inputs, Func<T, T> f) {
return inputs.ConvertAll((x) => f(x));
}
то
var outputs = Map(inputs, Square);
является законным. В этом случае он уже решил, что T
есть int
из того, что inputs
является List<int>
.
Теперь, чем глубже вопрос, почему это выше спецификация? То есть, почему группы методов не играют роли в разрешении параметров типа? Я думаю, это из-за таких случаев:
class Program {
public static T M<T>(Func<T, T> f) {
return default(T);
}
public static int F(int i) {
return i;
}
public static float F(float f) {
return f;
}
static void Main(string[] args) {
M(F); // which F am I?
}
}
Ответ 2
Вывод не подходит для вывода типа делегата, а не списка:
// this is also fine
var outputs3 = Map(inputs, new Func<int, int>(Square));
// more calls that compile correctly
var outputs4 = Map(inputs, x => Square(x));
var outputs5 = Map(inputs, x => x * x);
Func<int, int> t = Square;
var outputs6 = Map(inputs, t);
Я не знаю, почему, хотя, возможно, нет только неявного typecast от подписи Square
до Func<Int32, Int32>
? Кажется странным, что Func<int, int> t = Square;
действителен, но компилятор не может сделать скачок самостоятельно... Ошибка, может быть?
Ответ 3
После небольшого копания, я обнаружил, что ваше подозрение относительно другого ответа верное. Вот что говорит спецификация С# 3.0:
7.4.2.1 - для каждого из аргументов метода Ei: если Ei анонимный функции или группы методов, явный вывод типа параметра (7.4.2.7) сделал... 7.4.2.7 -... Если E - явно типизированная анонимная функция с типы параметров U1... Uk и T являются тип делегата с типами параметров V1... Vk, то для каждого Ui точный вывод (§7.4.2.8) сделан из Ui для соответствующего Vi.
Другими словами, анонимные методы и группы методов (квадрат) могут вызывать явно типы параметров. Я думаю, что оправдание в конце ответа, на которое вы ссылались, суммирует его хорошо. Поскольку вывод типа не всегда может быть сделан неявно из группы методов, компилятор даже не пытается его выполнить в спецификации.
Ответ 4
Причина, по которой это не работает, заключается в том, что для С# делать вывод типа в методе, он должен знать тип делегата на другом конце преобразования. Но на данный момент целевой тип делегата до сих пор не полностью известен - только T (int) известен, T2 все еще не разрешен.
Func<int, int> f = Square;
//works because we provided the destination type
//of the conversion from Square to delegate
Map(inputs, i => Square(i));
//works because the lambda follows the actual method call
//and determines its own return type
Ответ 5
Также работает следующее: Я не знаю, почему:
var outputs = Map(inputs, i => Square(i));