Чому Var не можна призначити анонімний метод?


139

У мене є такий код:

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
Зауважте, що ваш аргумент 4 аргументів у .NET 4 Func<>приймає до 16 аргументів.
Ентоні Пеграм

Дякуємо за роз’яснення. Я використовую .NET 3.5.
Марлон

9
Чому б змусити компілятор думати, що це Func<string, bool>? Це схоже Converter<string, bool>на мене!
Бен Войгт


3
іноді мені не вистачає VB ..Dim comparer = Function(value$) value <> "0"
Slai

Відповіді:


155

Інші вже вказували, що існує нескінченно багато можливих типів делегатов, які ви могли б мати на увазі; що таке особливий , Funcщо він заслуговує того, щоб бути за замовчуванням замість Predicateабо Actionбудь-якої іншої можливості? А для лямбдасів, чому очевидно, що наміром є вибір форми делегата, а не форма дерева виразів?

Але ми могли б сказати, що Funcце особливе, і що висновок типу лямбда або анонімного методу є функцією чогось. У нас все ще будуть усілякі проблеми. Які типи хотіли б зробити для наступних випадків?

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

Немає жодного Func<T>типу, який би відповідав на щось.

var x2 = y=>123;

Ми не знаємо тип формального параметра, хоча ми знаємо повернення. (Або ми? Чи повернення int? Long? Short? Byte?)

var x3 = (int y)=>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 });

і тепер умовивід типу методу працює, що таке тип func.


43
Коли ти збираєшся складати свої відповіді на відповідь у книгу? Я б купив його :)
Метт Грір,

13
Я другий на пропозицію книги Еріка Ліпперта з відповідей. Запропонована назва: "Роздуми зі стека"
Адам Ракіс

24
@Eric: Хороша відповідь, але трохи вводити в оману, щоб проілюструвати це як щось, що неможливо, оскільки це справді працює в D. Це просто те, що ви, хлопці, не вибрали надавати делегатам літерали власний тип, а замість цього зробили їх залежними у їх контекстах ... тому відповідь ІМХО повинна бути "тому що ми це зробили" більше, ніж усе інше. :)
користувач541686

5
@ab абстрактdissonance Зауважу також, що компілятор є відкритим кодом. Якщо ви дбаєте про цю функцію, то можете внести необхідний час і зусилля, щоб це відбулося. Я закликаю вас надіслати запит на тягнення.
Ерік Ліпперт

7
@Ab абстрактDissonance: ми оцінювали витрати з огляду на обмежені ресурси: розробники та час. Ця відповідальність не була дана богом; це було накладено віце-президентом підрозділу розробників. Думка про те, що команда 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>функціонально еквівалентний а Func<T, bool>, вони все-таки різні типи на рівні системи .NET. Отже, компілятор не може однозначно вирішити тип делегата, і повинен вивести з ладу умовивід типу.


1
Я впевнений, що дуже багато інших людей в Microsoft теж знають точно. ;) Але так, ви натякаєте на головну причину, тип часу компіляції неможливо визначити, оскільки його немає. Розділ 8.5.1 мовної специфікації спеціально висвітлює цю причину заборони використання анонімних функцій у неявно набраних оголошеннях змінних.
Ентоні Пеграм

3
Так. І ще гірше, що для лямбда ми навіть не знаємо, чи йдеться про тип делегата; це може бути дерево виразів.
Ерік Ліпперт

Для тих , хто зацікавлений, я написав трохи більше про це і як C # та F # наближається контраст в mindscapehq.com/blog/index.php/2011/02/23 / ...
itowlson

чому компілятор не може просто створити новий унікальний тип, як C ++, для своєї лямбда-функції
Weipeng L,

Чим вони відрізняються "на рівні системи типу .NET"?
arao6

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 може використовуватися лише тоді, коли локальна змінна оголошена та ініціалізована в тому самому операторі; змінна не може бути ініціалізована до нуля, або до групи методів або анонімної функції.
  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);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.