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

Вывод общего типа С# 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 без кастинга, так есть ли законная причина, по которой компилятор не мог также сделать то же самое через вывод типа?

4b9b3361

Ответ 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)) не может быть выведен.