Подтвердить что ты не робот

.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 - передача делегата в качестве параметра функции?

4b9b3361

Ответ 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));