Вывод универсального типа C# 3.0 – передача делегата в качестве параметра функции
-
03-07-2019 - |
Вопрос
Мне интересно, почему компилятор C# 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
без приведения, так есть ли законная причина, по которой компилятор не может сделать то же самое посредством вывода типа?
Решение
Возможно, это внесет ясность:
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's T?
}
Поскольку общая проблема была неразрешимой, они предпочитали оставлять нерешенными конкретные проблемы, для которых существует решение.
В результате этого решения вы не будете добавлять перегрузку для метода и получать массу путаницы типов со стороны всех вызывающих, которые привыкли к группе методов с одним членом.Я думаю, это хорошо.
Другие советы
Причина в том, что если тип когда-либо расширится, вероятность сбоя быть исключена.т. е. если к типу добавляется метод foo(string), это никогда не должно иметь значения для существующего кода - до тех пор, пока содержимое существующих методов не изменится.
По этой причине, даже если существует только один метод foo, ссылку на foo (известную как группа методов) нельзя привести к делегату, не зависящему от типа, например Action<T>
но только для делегата конкретного типа, такого как Action<int>
.
Это немного странно, да.Спецификацию вывода типов C# 3.0 трудно читать, и в ней есть ошибки, но она выглядит вроде должно работать.Я считаю, что на первом этапе (раздел 7.4.2.1) произошла ошибка - в первом пункте не следует упоминать группы методов (поскольку они не охватываются явным выводом типа параметра (7.4.2.7) - что означает, что следует использовать Вывод типа вывода (7.4.2.6).Что выглядит вроде должно работать - но очевидно, что это не так :(
Я знаю, что MS стремится улучшить спецификацию вывода типов, поэтому она может стать немного яснее.Я также знаю, что, независимо от сложности чтения, существуют ограничения на группы методов и вывод типов - ограничения, которые могут быть особыми случаями, когда группа методов на самом деле представляет собой только один метод.
У Эрика Липперта есть запись в блоге: вывод типа возвращаемого значения не работает с группами методов который похожий в этом случае - но здесь нас интересует не тип возвращаемого значения, а только тип параметра.возможно, что другие сообщения из его серии выводов типа хотя может поможет.
Имейте в виду, что задание
Action<int> f = foo;
уже содержит много синтаксического сахара.Компилятор фактически генерирует код для этого оператора:
Action<int> f = new Action<int>(foo);
Соответствующий вызов метода компилируется без проблем:
bar(new Action<int>(foo));
Кстати, то же самое касается и помощи компилятору в определении аргумента типа:
bar<int>(foo);
Итак, все сводится к вопросу: почему сахар в операторе присваивания, а не в вызове метода?Я должен был бы догадаться, что это потому, что сахар в назначении однозначен, возможна только одна замена.Но в случае вызовов методов авторам компилятора уже приходилось сталкиваться с проблемой разрешения перегрузки.Правила которого достаточно сложны.Вероятно, они просто не додумались до этого.
Для полноты картины это не относится только к C#:Тот же код 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))» не может быть выведен.