Відображаючи назву параметра: зловживання C # лямбда-виразами або синтаксичну яскравість?


428

Я дивлюся на компонент Gv MvcContrib і мене захоплює, але в той же час відштовхується синтаксичним трюком, який використовується в синтаксисі Grid :

.Attributes(style => "width:100%")

Синтаксис вище встановлює атрибут стилю створеного HTML width:100%. Тепер, якщо ви звернете увагу, "стиль" ніде не вказаний, виводиться з назви параметра в виразі! Мені довелося розібратися в цьому і виявив, де відбувається "чарівництво":

Hash(params Func<object, TValue>[] hash)
{
    foreach (var func in hash)
    {
        Add(func.Method.GetParameters()[0].Name, func(null));
    }
}

Таким чином, код використовує формальний час компіляції та ім’я параметрів для створення словника пар атрибутів імен-значень. Отримана синтаксична конструкція справді дуже виразна, але в той же час дуже небезпечна. Загальне використання лямбда-виразів дозволяє замінити імена, що вживаються без побічного ефекту. Я бачу приклад у книзі, в якій сказано, що collection.ForEach(book => Fire.Burn(book))я знаю, що можу написати у своєму коді, collection.ForEach(log => Fire.Burn(log))і це означає те саме . Але з раптовим синтаксисом MvcContrib Grid я знаходжу код, який активно виглядає і приймає рішення на основі імен, які я обираю для своїх змінних!

Так це звичайна практика для спільноти C # 3.5 / 4.0 та любителів лямбдаських виразів? Або це шахрай, який не повинен хвилюватися?


34
Я заперечую, що це виглядає очевидно, якщо ви готові дивитись на наміри коду, а не просто розбирати синтаксис. Що, якщо ви читаєте хороший код, це те, що ви все одно повинні робити. Синтаксис - це лише засіб наміру, і я заперечую, що це код, що розкриває намір.
Джеймі Пенні

190
Я просто запитав Андерса (та решту дизайнерської команди), що вони думають. Скажімо, результати не були надруковані в газеті для сімейного відпочинку.
Ерік Ліпперт

22
На даний момент у C # відсутній чистий, легкий синтаксис карт, особливо карти, передані функції. Різні динамічні мови (Ruby, Python, JavaScript, ColdFusion 9) мають певний чи інший синтаксис для цього.
yfeldblum

28
О, я вважаю, це дивиться чудово. Що стосується синтаксису карт, так, було б чудово, якби ми могли зробити нові {{"Do", "Aer"}, {"Re", "gold sun"} ...} і компілятор зробить висновок про побудова карти так само, як нова [] {1, 2,3} передбачає побудову масиву int. Ми розглядаємо такі речі.
Ерік Ліпперт

17
Ерік Ліпперт, я дуже поважаю тебе, але я думаю, що ти і група (включаючи Андерса, якого я поважаю ФРЕЙКІН 'ТРЕМЕНДОЛЬНО) б'ють надто суворо. Як ви визнаєте, у C # немає чіткого синтаксису карт, а деякі інші язики (наприклад, Ruby) мають чудові. Цей хлопець знайшов спосіб отримати потрібний синтаксис. Я надаю вам подібні способи висловити це, які майже такі ж виразні, як і його синтаксис із меншою кількістю недоліків. Але його синтаксис і той факт, що він так наполегливо працював, щоб зрозуміти, свідчить про необхідність вдосконалення мови. Минуле виконання означає, що ви, хлопці, створите для нього щось чудове.
Charlie Flowers

Відповіді:


147

Це поганий інтероп. Наприклад, розглянемо цей приклад C # - F #

C #:

public class Class1
{
    public static void Foo(Func<object, string> f)
    {
        Console.WriteLine(f.Method.GetParameters()[0].Name);
    }
}

F #:

Class1.Foo(fun yadda -> "hello")

Результат:

"arg" друкується (не "yadda").

Як результат, дизайнерам бібліотеки слід або уникати подібних "зловживань", або, принаймні, забезпечувати "стандартне" перевантаження (наприклад, що приймає ім'я рядка як додатковий параметр), якщо вони хочуть мати хороший інтероп для всіх мов .Net.


11
Ви цього не робите. Ця стратегія просто не портативна. (Якщо це допомагає, як, наприклад, інший спосіб, F # може бути здатний перевантажувати методи, що відрізняються лише типом повернення (це може зробити висновок типу). Це може бути виражено в CLR. Але F # відключає це, здебільшого тому, що якщо Ви це зробили, ці API не можна дзвонити з C #.) Якщо мова йде про interop, завжди є компроміси на "крайових" функціях щодо того, які вигоди ви отримуєте від того, який інтероп ви торгуєте.
Брайан

32
Мені не подобається, що не сумісність є причиною чогось не робити. Якщо сумісність є вимогою, тоді це робити, якщо ні, то навіщо це турбуватися? Це YAGNI IMHO.
Джон Фаррелл

15
@jfar: В .NET CLR інтероперабельність землі має зовсім новий вимір, оскільки збірки, згенеровані в будь-якому компіляторі, повинні використовуватися з будь-якої іншої мови.
Рем Русану

25
Я погоджуюся, що вам не потрібно бути сумісними з CLS, але це здається гарною ідеєю, якщо ви пишете бібліотеку або керуєте (і фрагмент, який починається з цього пункту, з сітки, так?) Інакше ви просто обмежуєте ваша аудиторія / база клієнтів
JMarsch

4
Можливо, варто змінити: Func <об'єкт, рядок> на Вираження << Функц <об'єкт, рядок >> І якщо ви обмежите праву частину виразу просто константами, ви можете мати реалізацію, яка робить це: public static IDictionary <string, string> Hash (параметри Вираз <Func <об'єкт, рядок >> [] хеш) {Словник <рядок, рядок> значення = новий словник <рядок, рядок> (); foreach (var func в хеші) {значення [func.Parameters [0] .Name] = (string) ((ConstantExpression) func.Body) .Value; } повернення значень; }
davidfowl

154

Я вважаю це дивним не стільки через назву , скільки через те, що лямбда непотрібна ; він може використовувати анонімний тип і бути більш гнучким:

.Attributes(new { style = "width:100%", @class="foo", blip=123 });

Це шаблон, який використовується в більшості ASP.NET MVC (наприклад), і має інші можливості ( застереження , зауважте також думки Айенда, якщо ім'я є магічним значенням, а не конкретним для абонента)


4
Це також має проблеми інтероп; не всі мови підтримують створення анонімного типу, такого як на ходу.
Брайан

5
Мені подобається, як ніхто насправді не відповідав на питання, натомість люди наводять аргумент "це краще". : p Це зловживання чи ні?
Сем Шафран

7
Мені було б цікаво побачити реакцію Еріка Ліпперта на це. Тому що це код FRAMEWORK . І це так само жахливо.
Метт Гінзе

26
Я не думаю, що проблема полягає в читанні після написання коду. Я думаю, що справжньою проблемою є вивченість коду. Що ти будеш думати, коли каже твоя інтелігенція. Розподіляє (об’єкт obj) ? Вам доведеться прочитати документацію (яку ніхто не хоче робити), оскільки ви не знатимете, що передати методу. Я не думаю, що це насправді краще, ніж приклад у питанні.
NotDan

2
@Arnis - чому більш гнучка: вона не покладається на імплементовану назву параметра, яка може (не цитуйте мене) викликати проблеми з деякими реалізаціями лямбда (інші мови) - але ви також можете використовувати звичайні об'єкти з визначеними властивостями. Наприклад, у вас може бути HtmlAttributesклас із очікуваними атрибутами (для intellisense) та просто ігнорувати ті, у кого nullзначення ...
Марк Гравелл

137

Просто хотів кинути свою думку (я автор компонента сітки MvcContrib).

Це, безумовно, зловживання мовою - в цьому немає сумнівів. Однак я б не вважав це протилежним до інтуїтивного - коли ви дивитесь на дзвінокAttributes(style => "width:100%", @class => "foo")
я думаю, що це досить очевидно, що відбувається (це, звичайно, не гірше, ніж підхід анонімного типу). З точки зору інтелігенції, я згоден, це досить непрозоро.

Для тих, хто цікавиться, деяка довідкова інформація про його використання у MvcContrib ...

Я додав це до сітки як особисте вподобання - мені не подобається використання анонімних типів як словників (наявність параметра, який приймає "об'єкт", так само непрозора, як та, яка приймає парами Func []), а ініціалізатор колекції словників - це швидше багатослівний (я також не є прихильником багатослівних інтерфейсів, наприклад, зв'язати декілька викликів на атрибут ("стиль", "дисплей: жоден"). Атрибут ("клас", "foo") тощо)

Якби C # мав менш довершений синтаксис для словникових літералів, я б не заважав включати цей синтаксис у компонент сітки :)

Я також хочу зазначити, що використання цього в MvcContrib є абсолютно необов’язковим - це методи розширення, які обтягують перевантаження, які замість нього беруть ідентифікатор. Я думаю, що важливо, що якщо ви надаєте такий метод, ви також повинні підтримувати більш "звичайний" підхід, наприклад, для взаємодії з іншими мовами.

Крім того, хтось згадав про "відображення накладних витрат", і я просто хотів зазначити, що насправді не так багато витрат на цей підхід - немає жодної рефлексії виконання або компіляції виразів (див. Http://blog.bittercoder.com /PermaLink,guid,206e64d1-29ae-4362-874b-83f5b103727f.aspx ).


1
Я також намагався дещо глибше вирішити деякі проблеми, порушені тут, у своєму блозі: jeremyskinner.co.uk/2009/12/02/lambda-abuse-the-mvccontrib-hash
Джеремі Скіннер

4
Він не менш непрозорий в Intellisense, ніж анонімні об'єкти.
Бред Вілсон

22
+1 для згадки про те, що інтерфейс додається за допомогою додаткових методів розширення. Користувачі, які не користуються C # (і будь-хто, кого образили зловживання мовою), можуть просто утриматися від його використання.
Jørn Schou-Rode

50

я волів би

Attributes.Add(string name, string value);

Це набагато чіткіше і стандартніше, і нічого не можна отримати, використовуючи лямбда.


20
Це все-таки? html.Attributes.Add("style", "width:100%");не читає так добре, як style = "width:100%"(фактично генерований html), тоді style => "width:100%"як дуже близький до того, як він виглядає в отриманому HTML.
Джеймі Пенні

6
Їх синтаксис дозволяє виконувати такі трюки, як .Attributes (id => 'foo', @class => 'bar', style => 'width: 100%'). Підпис функції використовує синтаксис парами для змінної кількості аргументів: Атрибути (парами Func <об'єкт, об'єкт> [] аргументи). Це дуже потужно, але мені знадобилося досить часу, щоб зрозуміти, що це робить.
Рем Русану

27
@Jamie: Спроба зробити код C # схожим на HTML-код буде поганою причиною для дизайнерських рішень. Вони абсолютно різні мови для абсолютно різних цілей, і вони не повинні виглядати однаково.
Гуффа

17
Анонімний об’єкт, можливо, так само добре використовувався, не приносячи шкоди "красі"? .Attributes (new {id = "foo", @class = "bar", style = "width: 100%"}) ??
Funka

10
@Guffa Чому це було б поганою причиною для дизайнерських рішень? Чому вони не повинні виглядати однаково? Чи повинні вони цілеспрямовано виглядати інакше? Я не кажу, що ви помиляєтесь, я просто кажу, що ви, можливо, захочете більш детально розробити свою думку.
Саманта Бранхам

45

Ласкаво просимо в Rails Land :)

З цим насправді немає нічого поганого, доки ви знаєте, що відбувається. (Саме тоді, коли подібні речі недостатньо задокументовані, виникає проблема).

Вся рамка Rails побудована на ідеї конвенції щодо конфігурації. Позначаючи речі певним чином, клює вас до конвенції, яку вони використовують, і ви отримуєте безліч функцій безкоштовно. Дотримуючись конвенції про іменування, ви перейдете куди швидше. Вся справа працює блискуче.

Ще одне місце, де я бачив такий трюк, - це у твердженнях викликів методів у Moq. Ви переходите в лямбду, але лямбда ніколи не виконується. Вони просто використовують вираз, щоб переконатися, що виклик методу стався, і викинули виняток, якщо ні.


3
Я трохи вагався, але я згоден. Окрім накладних рефлексій, немає значної різниці між використанням рядка, як у Add () та використанням назви параметра лямбда. Принаймні, що я можу придумати. Ви можете вимкнути його і ввести "sytle", не помічаючи обох способів.
Саманта Бранхам

5
Я не міг зрозуміти, чому це мені не дивно, і тоді я згадав Рейлів. : D
Еллін

43

Це жахливо на більш ніж одному рівні. І ні, це не що інше, як Рубі. Це зловживання C # і .Net.

Було багато пропозицій, як це зробити більш прямолінійним способом: кортежі, анонімні типи, вільний інтерфейс тощо.

Що робить це так погано, що це його справедливий шлях до власного блага:

  • Що станеться, коли вам потрібно зателефонувати на це від VB?

    .Attributes(Function(style) "width:100%")

  • Його повністю протилежний інтуїтивно зрозумілий інтеліссенс мало допоможе з’ясувати, як передавати речі.

  • Це зайво неефективне.

  • Ніхто не матиме поняття, як її підтримувати.

  • Який тип аргументу надходить до атрибутів Func<object,string>? Яким є такий намір розкрити. Що стосується вашої інтелігенційної документації: "Будь ласка, ігноруйте всі значення об'єкта"

Я думаю, ви цілком виправдані, що маєте почуття відрази.


4
Я б сказав - це абсолютно інтуїтивно. :)
Арніс Лапса

4
Ви кажете, що це не що інше, як Рубі. Але це жахливо багато, як синтаксис Рубі, для визначення ключів та значень хештелю.
Charlie Flowers

3
Код, який порушується при альфа-конверсії! Так!
Філ

3
@Charlie, синтаксично це схоже, семантично це інакше.
Сам Шафран

39

Я перебуваю в таборі "синтаксичної яскравості", якщо вони це чітко документують, і це виглядає це чудово круто, з цим іммо майже немає проблем!


10
Амінь, брате. Амінь (2-а амін вимагається дотриматись мінімальної тривалості для коментарів :)
Charlie Flowers

Один ваш коментар був набагато більше, ніж потрібно. Але потім, ви не можете просто амінь один раз, а потім залиште свій коментар: D
Спіт Сміт


21

Я навряд чи коли-небудь стикався з таким використанням. Я думаю, що це "недоречно" :)

Це не звичайний спосіб використання, він суперечить загальним умовам. Цей синтаксис має свої плюси і мінуси, звичайно:

Мінуси

  • Код не інтуїтивно зрозумілий (звичайні умовні умови різні)
  • Вона, як правило, неміцна (перейменування параметра порушить функціональність).
  • Тестувати трохи складніше (підробка API вимагатиме використання відображення в тестах).
  • Якщо вираз використовується інтенсивно, воно буде повільніше через необхідність аналізу параметра, а не лише значення (вартість відображення)

Плюси

  • Це читабельніше після того, як розробник налаштувався на цей синтаксис.

Підсумок - в публічному дизайні API я вибрав би більш чіткий спосіб.


2
@Elisha - ваші плюси і мінуси зворотні. Принаймні, я сподіваюся, ви не говорите, що Pro має код "не інтуїтивно зрозумілий". ;-)
Metro Smurf

У цьому конкретному випадку - ім’я параметра лямбда та параметр рядка є неміцними. Це як використання динаміки для розбору xml - це доречно, оскільки ви не можете бути впевнені в xml.
Арніс Лапса

19

Ні, це звичайно не звичайна практика. Це контр-інтуїтивно зрозуміло, немає ніякого способу просто подивитися на код, щоб зрозуміти, що він робить. Ви повинні знати, як це використовується, щоб зрозуміти, як воно використовується.

Замість надання атрибутів, використовуючи масив делегатів, методи ланцюжка були б зрозумілішими та ефективнішими:

.Attribute("style", "width:100%;").Attribute("class", "test")

Хоча це трохи більше набирати, це зрозуміло та інтуїтивно.


6
Дійсно? Я точно знав, що призначений той фрагмент коду, коли я дивився на нього. Це не так тупо, якщо ви не дуже суворі. Можна навести той же аргумент щодо перевантаження + для конкатенації рядків, і що ми завжди повинні використовувати метод Concat ().
Саманта Бранхем

4
@Stuart: Ні, ти точно не знав, ти лише здогадувався на основі використаних значень. Будь-хто може зробити це здогадком, але здогад - це не гарний спосіб розуміння коду.
Guffa

11
Я здогадуюсь, що використання .Attribute("style", "width:100%")дає мені style="width:100%", але, наскільки я знаю, це могло б дати мені foooooo. Я не бачу різниці.
Саманта Бранхем

5
"Відгадування на основі використаних значень" - ЗАВЖДИ те, що ви робите, дивлячись на код. Якщо ви зіткнулися з викликом на stream.close (), ви припускаєте, що він закриває потік, але він може також зробити щось зовсім інше.
Wouter Lievens

1
@Wouter: Якщо ви ВЖЕ Вгадуєте під час читання коду, у вас повинні виникнути великі труднощі з читанням коду ... Якщо я бачу дзвінок до методу під назвою "закрити", я можу зрозуміти, що автор класу не знає про умови іменування , тож я б дуже вагався сприймати що-небудь як належне про те, що робить метод.
Гуффа

17

Чи можу я скористатися цією фразою?

магічна лямбда (n): функція лямбда, яка використовується виключно для заміни магічної струни.


так ... це смішно. і, можливо, це магічна різновид, в сенсі відсутність безпеки часу компіляції, чи є місце, де це використання спричинятиме час виконання замість помилок часу компіляції?
Маслоу


16

Вся ця скандальність про "жахливість" - це купа давніх c # хлопців, які перебільшують реакцію (і я давній програміст C # і все ще дуже великий фанат мови). У цьому синтаксисі немає нічого страшного. Це просто спроба зробити синтаксис більш схожим на те, що ви намагаєтеся висловити. Чим менше "шуму" в чомусь синтаксисі, тим легше програміст може це зрозуміти. Зменшення шуму в одному рядку коду лише трохи допомагає, але нехай це накопичується через усе більше коду, і це виявляється суттєвою користю.

Це спроба автора прагнути до тих самих переваг, які дає вам DSL - коли код просто "схожий" на те, що ви намагаєтесь сказати, ви дійшли до магічного місця. Ви можете обговорити, чи добре це для інтеропу, чи достатньо приємно, ніж анонімні методи, щоб виправдати деякі «складності» вартості. Досить справедливо ... тому у своєму проекті ви повинні зробити правильний вибір, чи використовувати цей вид синтаксису. Але все-таки ... це розумна спроба програміста зробити те, що, врешті-решт, ми всі намагаємось зробити (чи усвідомлюємо ми це чи ні). І те, що ми всі намагаємось зробити, це таке: "Скажіть комп’ютеру, що ми хочемо, щоб він робив мовою, якомога ближчою до того, як ми думаємо про те, що хочемо робити."

Наблизившись до викладу наших інструкцій на комп’ютерах тим самим способом, який, як ми вважаємо, є ключовим фактором для того, щоб зробити програмне забезпечення більш доступним та точним.

EDIT: Я сказав, що "ключ до того, щоб зробити програмне забезпечення більш ретельним і точнішим", що є шалено наївною завищеною частиною єдиноборства. Я змінив його на "ключ".


12

Це одна з переваг дерев виразів - для додаткової інформації можна вивчити сам код. Ось як .Where(e => e.Name == "Jamie")можна перетворити в еквівалентний пункт SQL Where. Це розумне використання дерев виразів, хоча я б сподівався, що воно не піде далі від цього. Що-небудь більш складне, ймовірно, буде складніше, ніж код, який він сподівається замінити, тому я підозрюю, що це буде обмежувати себе.


Це правдивий момент, але правда в рекламі: LINQ має цілий набір атрибутів, таких як TableAttribute та ColumnAttribute, які роблять це більш законною справою. Також картографічне відображення linq розглядає назви класів та назви властивостей, які, мабуть, більш стійкі, ніж імена параметрів.
Рем Русану

Я згоден з вами там. Я трохи змінив свою позицію з цього приводу, прочитавши, що Ерік Ліпперт / Андерс Гельсберг / тощо мав сказати з цього приводу. Думав, що я залишу цю відповідь, оскільки вона все ще дещо корисна. Для чого це варто, я вважаю, що цей стиль роботи з HTML є приємним, але він не відповідає мові.
Джеймі Пенні

7

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

Expression<Func<object, string>>

Я вважаю, що ви дійсно хочете замість делегата (ви використовуєте лямбда для отримання імен обох сторін) Дивіться наївну реалізацію нижче:

public static IDictionary<string, string> Hash(params Expression<Func<object, string>>[] hash) {
    Dictionary<string, string> values = new Dictionary<string,string>();
    foreach (var func in hash) {
        values[func.Parameters[0].Name] = ((ConstantExpression)func.Body).Value.ToString();
    }
    return values;
}

Це може навіть вирішити проблему інтероп-перехресних мов, про яку було сказано раніше в потоці.


6

Код дуже розумний, але потенційно може викликати більше проблем, які він вирішує.

Як ви вже зазначали, тепер існує неясна залежність між назвою параметра (стилем) та атрибутом HTML. Перевірка часу компіляції не проводиться. Якщо ім'я параметра введено неправильно, на сторінці, ймовірно, не буде повідомлення про помилку виконання, але набагато складніше знайти логічну помилку (немає помилки, але неправильна поведінка).

Кращим рішенням буде мати член даних, який можна перевірити під час компіляції. Тож замість цього:

.Attributes(style => "width:100%");

код із властивістю Style може перевірити компілятор:

.Attributes.Style = "width:100%";

або навіть:

.Attributes.Style.Width.Percent = 100;

Це більше роботи для авторів коду, але цей підхід використовує перевагу сильної перевірки типу C #, що допомагає в першу чергу запобігти потраплянню помилок у код.


3
Я ціную перевірку за час збирання, але думаю, що це зводиться до питання думки. Можливо, щось на кшталт нових Attributes () {Style: "width: 100%"} переможе більше людей до цього, оскільки це більш лаконічно. Тим не менш, реалізація всього, що дозволяє HTML, - це величезна робота, і я не можу звинувачувати когось у тому, що він використовує класи рядків / лямбда / анонімних.
Саманта Бранхем

5

насправді це здається Ruby =), принаймні для мене використання статичного ресурсу для подальшого динамічного "пошуку" не підходить для міркувань дизайну api, сподіваюся, що цей розумний трюк не є обов'язковим у цьому api.

Ми можемо успадкувати від IDictionary (чи ні) та надати індексатор, який веде себе як масив php, коли вам не потрібно додавати ключ для встановлення значення. Це буде коректним використанням .net семантики не просто c #, а ще потрібна документація.

сподіваюся, що це допомагає


4

На мою думку, це зловживання лямбдами.

Щодо яскравості синтаксису, я вважаю style=>"width:100%"просто заплутаною. Особливо через =>замість=


4

ІМХО, це класний спосіб зробити це. Всі ми любимо той факт, що називання контролера класу зробить його контролером в MVC? Тож бувають випадки, коли називання має значення.

Також намір тут дуже зрозумілий. Дуже легко зрозуміти, що .Attribute( book => "something")призведе до book="something"і .Attribute( log => "something")призведе доlog="something"

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


4
Називання контролера класу не буде робити присідання, хоча якщо ви теж не успадкуєте контролер ...
Jordan Wallwork

3

Якщо імена методу (func) вдало обрані, то це геніальний спосіб уникнути головних болів у підтримці (тобто: додати новий функцію, але забув додати його до списку відображення функцій-параметрів). Звичайно, вам потрібно сильно документувати це, і вам краще буде автоматично генерувати документацію для параметрів з документації для функцій цього класу ...


1

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.