Вывод универсального типа C# 3.0 – передача делегата в качестве параметра функции

StackOverflow https://stackoverflow.com/questions/407983

Вопрос

Мне интересно, почему компилятор 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))» не может быть выведен.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top