Вывод общего типа С# 3.0 - передача делегата в качестве параметра функции
Мне интересно, почему компилятор С# 3.0 не может вывести тип метода, когда он передается как параметр в общую функцию, когда он может неявно создавать делегат для того же метода.
Вот пример:
class Test
{
static void foo(int x) { }
static void bar<T>(Action<T> f) { }
static void test()
{
Action<int> f = foo; // I can do this
bar(f); // and then do this
bar(foo); // but this does not work
}
}
Я бы подумал, что смогу передать foo
в bar
и предоставить компилятору тип Action<T>
из сигнатуры передаваемой функции, но это не сработает. Однако я могу создать Action<int>
из foo
без кастинга, так есть ли законная причина, по которой компилятор не мог также сделать то же самое через вывод типа?
Ответы
Ответ 1
Возможно, это сделает его более ясным:
public class SomeClass
{
static void foo(int x) { }
static void foo(string s) { }
static void bar<T>(Action<T> f){}
static void barz(Action<int> f) { }
static void test()
{
Action<int> f = foo;
bar(f);
barz(foo);
bar(foo);
//these help the compiler to know which types to use
bar<int>(foo);
bar( (int i) => foo(i));
}
}
foo не является действием - foo - группа методов.
- В операторе присваивания компилятор может четко определить, о каком foo вы говорите, поскольку указан тип int.
- В инструкции barz (foo) компилятор может определить, о каком foo вы говорите, поскольку указан тип int.
- В инструкции bar (foo) это может быть любое foo с одним параметром - поэтому компилятор отказывается.
Изменить: я добавил два (более) способа помочь компилятору выяснить тип (т.е. как пропустить шаги вывода).
Из моего чтения статьи в ответе JSkeet решение не выводить тип, похоже, основано на сценарии взаимного вывода, например
static void foo<T>(T x) { }
static void bar<T>(Action<T> f) { }
static void test()
{
bar(foo); //wut T?
}
Поскольку общая проблема была неразрешимой, они решили оставить конкретные проблемы, где решение существует как нерешенное.
Как следствие этого решения, вы не будете добавлять перегрузку для метода и получать путаницу по множеству типов от всех вызывающих абонентов, которые используются для одной группы методов-членов. Я думаю, что это хорошо.
Ответ 2
Причиной является то, что если тип когда-либо расширяется, не должно быть возможности отказа. то есть, если к типу добавлен метод foo (string), он не должен иметь никакого значения для существующего кода - если содержимое существующих методов не изменяется.
По этой причине, даже если существует только один метод foo, ссылка на foo (известная как группа методов) не может быть передана делегату, не относящемуся к типу, например Action<T>
, а только к типу- конкретный делегат, такой как Action<int>
.
Ответ 3
Это немного странно, да. Спецификацию С# 3.0 для вывода типа трудно читать и имеет ошибки в ней, но похоже, что она должна работать. На первом этапе (раздел 7.4.2.1) я считаю, что есть ошибка - он не должен упоминать группы методов в первой пуле (поскольку они не охвачены явным указанием типа параметра (7.4.2.7), что означает, что он должен использовать вывода (7.4.2.6). Похоже, что он должен работать, но, очевидно, это не так: (
Я знаю, что MS хочет улучшить спецификацию для вывода типа, поэтому она может стать немного яснее. Я также знаю, что независимо от сложности его чтения существуют ограничения на группы методов и тип вывода - ограничения, которые могут быть специально обрезаны, когда группа методов фактически является единственным методом, по общему признанию.
У Эрика Липперта есть запись в блоге возвращаемый тип вывода, не работающий с группами методов, который похож на этот случай - но здесь мы не интересуется типом возврата, только по типу параметра. Возможно, что другие сообщения в серии записей типов могут помочь.
Ответ 4
Имейте в виду, что назначение
Action<int> f = foo;
уже есть много синтаксического сахара. Компилятор действительно генерирует код для этого оператора:
Action<int> f = new Action<int>(foo);
Соответствующий вызов метода компилируется без проблем:
bar(new Action<int>(foo));
Fwiw, поэтому помогает компилятору вывести аргумент типа:
bar<int>(foo);
Итак, это сводится к вопросу, почему сахар в инструкции присваивания, но не в вызове метода? Я должен был догадаться, что это потому, что сахар однозначен в задании, есть только одна возможная замена. Но в случае вызовов методов авторам компилятора уже приходилось иметь дело с проблемой разрешения перегрузки. Правила которого достаточно сложны. Они, вероятно, просто не обошли его.
Ответ 5
Просто для полноты, это не относится к С#: тот же код VB.NET сработает аналогично:
Imports System
Module Test
Sub foo(ByVal x As integer)
End Sub
Sub bar(Of T)(ByVal f As Action(Of T))
End Sub
Sub Main()
Dim f As Action(Of integer) = AddressOf foo ' I can do this
bar(f) ' and then do this
bar(AddressOf foo) ' but this does not work
End Sub
End Module
ошибка BC32050: параметр типа 'T' для 'Public Sub bar (Of T) (f As System.Action(Of T)) не может быть выведен.