Вывод универсального типа в C # 3.0 - передача делегата в качестве параметра функции - PullRequest
23 голосов
/ 02 января 2009

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

Ответы [ 5 ]

16 голосов
/ 03 января 2009

Может быть, это прояснит:

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?
  }

Поскольку общая проблема была неразрешимой, они решили оставить конкретные проблемы, где решение существует как нерешенные.

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

7 голосов
/ 03 января 2009

Смысл в том, что если тип расширяется, вероятность сбоя не должна быть. то есть, если к типу добавлен метод foo (string), он никогда не должен иметь значения для существующего кода - до тех пор, пока содержимое существующих методов не изменится.

По этой причине, даже если существует только один метод foo, ссылку на foo (известную как группа методов) нельзя привести к делегату, не зависящему от типа, например Action<T>, но только к типу. конкретный делегат, такой как Action<int>.

5 голосов
/ 03 января 2009

Имейте в виду, что задание

Action<int> f = foo;

уже содержит много синтаксического сахара. Компилятор фактически генерирует код для этого оператора:

Action<int> f = new Action<int>(foo);

Соответствующий вызов метода компилируется без проблем:

bar(new Action<int>(foo));

Кстати, это помогает компилятору выводить аргумент типа:

bar<int>(foo);

Так что все сводится к вопросу, почему сахар в операторе присваивания, а не в вызове метода? Я должен был бы предположить, что это потому, что сахар однозначен в задании, есть только одна возможная замена. Но в случае вызовов методов авторам компилятора уже приходилось сталкиваться с проблемой разрешения перегрузки. Правила которых довольно сложны. Они, вероятно, просто не удосужились.

5 голосов
/ 03 января 2009

Это немного странно, да. Спецификация C # 3.0 для вывода типов трудна для чтения и содержит ошибки, но она выглядит так, как она должна работать. На первом этапе (раздел 7.4.2.1) я считаю, что есть ошибка - она ​​не должна упоминать группы методов в первом пункте (поскольку они не охватываются явным выводом типа параметра (7.4.2.7) - что означает, что он должен использовать вывод типа вывода (7.4.2.6). Что выглядит так, как будто оно должно работать - но, очевидно, это не так: (

Я знаю, что MS стремится улучшить спецификации для вывода типов, так что это может стать немного яснее. Я также знаю, что, независимо от сложности чтения, существуют ограничения на группы методов и вывод типов - ограничения, которые могут быть в особом случае, если группа методов на самом деле является единственным методом, по общему признанию.

У Эрика Липперта есть запись в блоге о выводе типа возврата, не работающем с группами методов , что похоже на этот случай - но здесь нас не интересует только тип возвращаемого значения на тип параметра. Вполне возможно, что другие посты из его серии выводов типа могут помочь.

0 голосов
/ 04 июля 2011

Просто для полноты, это не относится к 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 Subbar (Of T) (f As System.Action (Of T))' не может быть выведен.

...