Почему С# не может вывести тип из этого, казалось бы, простого, очевидного случая

С учетом этого кода:

class C
{
    C()
    {
        Test<string>(A); // fine
        Test((string a) => {}); // fine
        Test((Action<string>)A); // fine

        Test(A); // type arguments cannot be inferred from usage!
    }

    static void Test<T>(Action<T> a) { }

    void A(string _) { }
}

Компилятор жалуется, что Test(A) не может определить T как string.

Мне кажется, это довольно простой случай, и я клянусь, что я использовал гораздо более сложный вывод в других общих полезных функциях и расширениях, которые я написал. Что мне здесь не хватает?

Обновление 1: это в компиляторе С# 4.0. Я обнаружил проблему в VS2010, и приведенный выше пример - из простейшего примера, сделанного мной в LINQPad 4.

Обновление 2: добавлено еще несколько примеров в список того, что работает.

Ответ 1

Test(A);

Это не удается, потому что единственный применимый метод (Test<T>(Action<T>)) требует вывода типа, и алгоритм вывода типа требует, чтобы каждый каждый аргумент был какого-то типа или был анонимной функцией. (Этот факт вытекает из спецификации алгоритма вывода типа (§7.5.2). Группа методов A не имеет никакого типа (даже если она может быть преобразована в соответствующий тип делегата), и это не анонимный функция.

Test<string>(A);

Это преуспевает, разница в том, что вывод типа не требуется для привязки Test, а группа методов A может быть преобразована в требуемый тип параметра делегирования void Action<string>(string).

Test((string a) => {});

Это преуспевает, разница заключается в том, что алгоритм вывода типа обеспечивает анонимные функции на первом этапе (§7.5.2.1). Параметры и возвращаемые типы анонимной функции известны, поэтому можно сделать вывод о явном типе параметров, и таким образом происходит корреляция между типами анонимной функции (void ?(string)) и параметром типа в типе делегирования Test параметр методов (void Action<T>(T)). Алгоритм не задан для групп методов, которые соответствовали бы этому алгоритму для анонимных функций.

Test((Action<string>)A);

Это преуспевает, причем разница заключается в том, что параметр группы непечатанных методов A передается типу, что позволяет выводить вывод типа Test в обычном порядке с выражением определенного типа в качестве единственного аргумента метода.

Я не думаю, что в теории почему-то невозможно было бы решить проблему перегрузки в группе методов A. Затем - если найдено единственное лучшее связывание - группе методов может быть дано такое же обращение, как анонимная функция. Это особенно верно в таких случаях, когда группа методов содержит ровно один кандидат и не имеет параметров типа. Но причина, по которой это не работает в С# 4, по-видимому, заключается в том, что эта функция не была разработана и реализована. Учитывая сложность этой функции, обыденность ее применения и существование трех простых обходов, я не собираюсь задерживать дыхание!

Ответ 2

Я думаю, это потому, что это двухступенчатый вывод:

  • Он должен сделать вывод, что вы хотите преобразовать A в общий делегат

  • Он должен определить, какой должен быть тип параметра делегата

Я не уверен, что это причина, но я подозреваю, что двухэтапный вывод не всегда является легким для компилятора.


Edit:

Просто догадка, но что-то говорит мне, что первым шагом является проблема. Компилятор должен вычислить, чтобы преобразовать в делегат с другим числом общих параметров, и поэтому он не может выводить типы параметров.

Ответ 3

Это выглядит как замкнутый круг для меня.

Test метод ожидает параметр типа делегата, построенный из родового типа Action<T>. Вместо этого вы передаете группу методов: Test(A). Это означает, что компилятор должен преобразовать ваш параметр в тип делегата (преобразование группы методов).

Но какой тип делегата? Чтобы узнать тип делегата, нам нужно знать T. Мы не указали его явно, поэтому компилятор должен определить его тип делегата.

Чтобы вывести параметры типа метода, нам нужно знать типы аргументов метода, в этом случае тип делегата. Компилятор не знает тип аргумента и, следовательно, терпит неудачу.

Во всех остальных случаях любой тип аргумента очевиден:

// delegate is created out of anonymous method,
// no method group conversion needed - compiler knows it Action<string>
Test((string a) => {});

// type of argument is set explicitly
Test((Action<string>)A); 

или параметр типа указан явно:

Test<string>(A); // compiler knows what type of delegate to convert A to

P.S. больше по типу вывода

Ответ 4

Вы передаете имя Метод A. Рамка .Net МОЖЕТ конвертировать его в Action, но она неявно и не несет за это ответственности.

Но все же имя метода NOT - явный объект Action<>. И поэтому он не будет выводить тип как тип Action.

Ответ 5

Я мог ошибаться, но я предполагаю, что реальная причина, по которой С# не может вывести тип из-за перегрузки метода и возникшей неоднозначности. Например, предположим, что у меня есть следующие методы: void foo (int) и void foo (float). Теперь, если я пишу var f = foo. Какой foo должен выбрать компилятор? Аналогично, эта проблема возникает с вашим примером, используя Test(foo).