Почему для var нельзя назначить анонимный метод?

У меня есть следующий код:

Func<string, bool> comparer = delegate(string value) {
    return value != "0";
};

Однако следующее не компилируется:

var comparer = delegate(string value) {
    return value != "0";
};

Почему компилятор не может определить, что это Func<string, bool>? Он принимает один строковый параметр и возвращает логическое значение. Вместо этого он дает мне ошибку:

Невозможно назначить анонимный метод неявно типизированная локальная переменная.

У меня есть одно предположение, и если версия var будет скомпилирована, она не будет иметь согласованности, если бы у меня было следующее:

var comparer = delegate(string arg1, string arg2, string arg3, string arg4, string arg5) {
    return false;
};

Вышеизложенное не имеет смысла, поскольку Func < > допускает только до 4 аргументов (в .NET 3.5, который я использую). Возможно, кто-то может прояснить проблему. Спасибо.

Ответ 1

Другие уже указали, что существует бесконечное число возможных типов делегатов, которые вы могли бы иметь в виду; что особенного в Func, что он заслуживает быть по умолчанию вместо Predicate или Action или любой другой возможности? И, для лямбда, почему очевидно, что намерение состоит в том, чтобы выбрать форму делегата, а не форму дерева выражений?

Но мы могли бы сказать, что Func является специальным и что предполагаемым типом лямбда-метода или анонимного является Func чего-то. У нас все еще были бы проблемы. Какие типы вы хотели бы вывести для следующих случаев?

var x1 = (ref int y)=>123;

Нет типа Func<T>, который принимает ссылку ref.

var x2 = y=>123;

Мы не знаем тип формального параметра, хотя мы знаем возврат. (Или мы? Является ли return int? Long? Short? Byte?)

var x3 = (int y)=>null;

Мы не знаем тип возврата, но он не может быть недействительным. Тип возврата может быть любым ссылочным типом или любым типом значения NULL.

var x4 = (int y)=>{ throw new Exception(); }

Опять же, мы не знаем тип возврата, и на этот раз он может быть недействительным.

var x5 = (int y)=> q += y;

Является ли это предполагаемым оператором lambda, возвращающим пустоту, или чем-то, что возвращает значение, которое было присвоено q? Оба являются законными; который мы должны выбрать?

Теперь вы можете сказать, ну, просто не поддерживайте ни одну из этих функций. Просто поддерживайте "обычные" случаи, когда типы могут быть разработаны. Это не помогает. Как это облегчает мою жизнь? Если функция иногда работает и иногда не работает, мне все же приходится писать код, чтобы обнаружить все эти ситуации сбоя и дать значимое сообщение об ошибке для каждого. Нам все равно придется указывать все это поведение, документировать его, писать тесты для него и т.д. Это очень дорогая функция, которая экономит пользователю, возможно, полдюжины нажатий клавиш. У нас есть лучшие способы повысить ценность языка, чем тратить много времени на создание тестовых примеров для функции, которая не работает в течение половины времени и не дает практически никакой пользы в тех случаях, когда она работает.

Ситуация, в которой это действительно полезно:

var xAnon = (int y)=>new { Y = y };

потому что для этой вещи нет "говорящего" типа. Но у нас есть эта проблема все время, и мы просто используем вывод метода типа для вывода типа:

Func<A, R> WorkItOut<A, R>(Func<A, R> f) { return f; }
...
var xAnon = WorkItOut((int y)=>new { Y = y });

и теперь метод вывода метода определяет, какой тип func.

Ответ 2

Только Эрик Липперт знает наверняка, но я думаю, потому что подпись типа делегата не однозначно определяет тип.

Рассмотрим ваш пример:

var comparer = delegate(string value) { return value != "0"; };

Вот два возможных вывода для того, что должно быть var:

Predicate<string> comparer  = delegate(string value) { return value != "0"; };  // okay
Func<string, bool> comparer = delegate(string value) { return value != "0"; };  // also okay

Какой должен быть компилятор? Нет веских оснований для выбора того или другого. И хотя Predicate<T> функционально эквивалентен Func<T, bool>, они все еще различаются на уровне системы типа .NET. Поэтому компилятор не может однозначно разрешить тип делегата и не должен выводить вывод типа.

Ответ 3

У Эрика Липперта есть старая сообщение об этом, где он говорит

И на самом деле спецификация С# 2.0 называет это. Группа методов выражения и анонимный метод выражения - это бесчисленные выражения в С# 2.0 и присоединяются лямбда-выражения их в С# 3.0. Поэтому незаконно для них появляться "голыми" на правая часть неявного декларация.

Ответ 4

Различные делегаты считаются разными. например, Action отличается от MethodInvoker, а экземпляр Action не может быть назначен переменной типа MethodInvoker.

Итак, учитывая анонимный делегат (или лямбда), например () => {}, это Action или MethodInvoker? Компилятор не может сказать.

Аналогично, если я объявляю тип делегата, принимающий аргумент string и возвращающий bool, как бы компилятор знал, что вам действительно нужен Func<string, bool> вместо моего типа делегата? Он не может вывести тип делегата.

Ответ 5

В MSDN указываются следующие вопросы относительно неявно типизированных локальных переменных:

  • var может использоваться только тогда, когда локальная переменная объявляется и инициализируется в том же самом выражении; переменная не может быть инициализирована нулем, или группе методов или анонимной функции.
  • Ключевое слово var указывает компилятору вывести тип переменной из выражения в правой части инструкции инициализации.
  • Важно понимать, что ключевое слово var не означает "вариант" и не указывает на то, что переменная свободно набрана или поздняя. Это просто означает, что компилятор определяет и присваивает наиболее подходящий тип.

Ссылка MSDN: неявно типизированные локальные переменные

Учитывая следующее: Анонимные методы:

  • Анонимные методы позволяют вам опустить список параметров.

Ссылка MSDN: Анонимные методы

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

Ответ 6

Как об этом?

var item = new
    {
        toolisn = 100,
        LangId = "ENG",
        toolPath = (Func<int, string, string>) delegate(int toolisn, string LangId)
        {
              var path = "/Content/Tool_" + toolisn + "_" + LangId + "/story.html";
              return File.Exists(Server.MapPath(path)) ? "<a style=\"vertical-align:super\" href=\"" + path + "\" target=\"_blank\">execute example</a> " : "";
        }
};

string result = item.toolPath(item.toolisn, item.LangId);