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

142

У меня такой код:

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, что я и использую). Возможно, кто-нибудь сможет прояснить проблему. Спасибо.

Марлон
источник
3
Обратите внимание на ваш аргумент с четырьмя аргументами , в .NET 4 можно Func<>принять до 16 аргументов.
Энтони Пеграм,
Благодарю за разъяснение. Я использую .NET 3.5.
Марлон
9
Почему компилятор может подумать, что это Func<string, bool>? Converter<string, bool>Для меня это похоже на!
Бен Фойгт
3
иногда я скучаю по VB ..Dim comparer = Function(value$) value <> "0"
Slai

Ответы:

156

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

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

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

Нет такого Func<T>типа, который мог бы что-либо использовать.

var x2 = y=>123;

Мы не знаем тип формального параметра, но знаем возвращаемый результат. (Или мы? Возвращаемый int? Длинный? Короткий? Байт?)

var x3 = (int y)=>null;

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

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

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

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

Предполагается, что это будет лямбда-выражение, возвращающее пустое значение, или что-то, что возвращает значение, присвоенное 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 });

и теперь вывод типа метода определяет, что это за тип функции.

Эрик Липперт
источник
44
Когда вы собираетесь скомпилировать свои ответы SO в книгу? Я бы купил это :)
Мэтт Грир
13
Я поддерживаю предложение о книге Эрика Липперта с ответами SO. Предлагаемое название: «Размышления из стопки»
Адам Рэкис
25
@Eric: Хороший ответ, но иллюстрировать это как то, что невозможно, это немного вводит в заблуждение, поскольку на самом деле это прекрасно работает в D. Просто вы, ребята, не решили давать литералам делегатов их собственный тип, а вместо этого заставили их зависеть в их контекстах ... так что ИМХО ответ должен быть «потому что мы так сделали» больше, чем что-либо еще. :)
user541686 01
5
@abstractdissonance Отмечу также, что компилятор имеет открытый код. Если вам небезразлична эта функция, вы можете потратить необходимое время и усилия на ее реализацию. Я рекомендую вам отправить запрос на перенос.
Эрик Липперт
7
@AbstractDissonance: Мы измерили стоимость с точки зрения ограниченных ресурсов: разработчиков и времени. Эта ответственность не была дана Богом; его наложил вице-президент отдела разработчиков. Представление о том, что команда C # каким-то образом может игнорировать бюджетный процесс, является странным. Уверяю вас, компромиссы были и остаются в результате тщательного и вдумчивого рассмотрения экспертов, которые имели в виду высказанные сообщества C # сообщества, стратегическую миссию Microsoft и свой собственный отличный дизайн.
Эрик Липперт
29

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

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

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>функционально a эквивалентно a Func<T, bool>, они по-прежнему являются разными типами на уровне системы типов .NET. Таким образом, компилятор не может однозначно определить тип делегата и должен не выполнить вывод типа.

Itowlson
источник
1
Я уверен, что многие другие люди в Microsoft также знают наверняка. ;) Но да, вы ссылаетесь на главную причину, что тип времени компиляции не может быть определен, потому что его нет. В разделе 8.5.1 спецификации языка особо подчеркивается эта причина запрета использования анонимных функций в объявлениях неявно типизированных переменных.
Энтони Пеграм,
3
Ага. И что еще хуже, для лямбда-выражений мы даже не знаем, идет ли это к типу делегата; это может быть дерево выражений.
Эрик Липперт
Для всех, кому интересно, я написал немного больше об этом и о том, как C # и F # подходят к контрасту, на mindscapehq.com/blog/index.php/2011/02/23/…
itowlson
почему компилятор не может просто создать новый уникальный тип, как это делает C ++ для своей лямбда-функции
Weipeng L
Чем они отличаются «на уровне системы типов .NET»?
arao6 01
6

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

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

Брайан Расмуссен
источник
И это подчеркивается в разделе 8.5.1 спецификации языка. «Выражение инициализатора должно иметь тип времени компиляции», чтобы его можно было использовать для неявно типизированной локальной переменной.
Энтони Пеграм,
5

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

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

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

Стивен Клири
источник
2

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

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

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

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

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

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

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

нибблер
источник
1

Мой пост не отвечает на фактический вопрос, но он отвечает на основной вопрос:

«Как мне избежать необходимости печатать какой-нибудь унылый типаж Func<string, string, int, CustomInputType, bool, ReturnType>[1]

Как я и ленивый программист, я экспериментировал с использованием Func<dynamic, object>-, которое принимает единственный входной параметр и возвращает объект.

Для нескольких аргументов вы можете использовать это так:

dynamic myParams = new ExpandoObject();
myParams.arg0 = "whatever";
myParams.arg1 = 3;
Func<dynamic, object> y = (dynObj) =>
{
    return dynObj.arg0.ToUpper() + (dynObj.arg1 * 45); //screw type casting, amirite?
};
Console.WriteLine(y(myParams));

Совет: вы можете использовать Action<dynamic> если вам не нужно возвращать объект.

Да, я знаю, что это, вероятно, противоречит вашим принципам программирования, но это имеет смысл для меня и, возможно, некоторых программистов Python.

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


[1] Предполагается, что вы не вызываете метод, который требует предопределенного Funcпараметра в качестве параметра, и в этом случае вам придется ввести эту уродливую строку: /

Амвросий Люн
источник
0

Как насчет этого?

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);
ммм
источник