Отражение имени параметра: злоупотребление лямбда-выражениями C # или синтаксический блеск? - PullRequest
422 голосов
/ 12 ноября 2009

Я смотрю на компонент * Grid 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 и любителей лямбда-выражений? Или мне не стоит беспокоиться о мошеннике-мошеннике?

Ответы [ 21 ]

154 голосов
/ 12 ноября 2009

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

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

Этот шаблон используется во многих ASP.NET MVC (например) и имеет других применений ( предостережение , обратите внимание также Мысли Айенде если имя является магическим значением, а не специфичным для вызывающего абонента)

146 голосов
/ 12 ноября 2009

Это плохое взаимодействие. Например, рассмотрим этот пример 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»).

В результате разработчики библиотек должны либо избегать такого рода «злоупотреблений», либо, по крайней мере, предоставлять «стандартную» перегрузку (например, которая принимает имя строки в качестве дополнительного параметра), если они хотят иметь хорошее взаимодействие. Чистые языки.

137 голосов
/ 02 декабря 2009

Просто хотел выкинуть на мой взгляд (я автор компонента сетки MvcContrib).

Это определенно языковое злоупотребление - без сомнения. Тем не менее, я бы не стал считать это противоречащим интуиции - когда вы смотрите на вызов Attributes(style => "width:100%", @class => "foo")
Я думаю, что это довольно очевидно, что происходит (это, конечно, не хуже, чем подход анонимного типа). С точки зрения интеллигенции, я согласен, что это довольно непрозрачно.

Для тех, кто заинтересован, некоторая справочная информация о его использовании в MvcContrib ...

Я добавил это в таблицу как личное предпочтение - мне не нравится использование анонимных типов в качестве словарей (параметр, принимающий «объект», так же непрозрачен, как параметр, принимающий параметры Func []) и словарь инициализатор коллекции довольно многословен (я также не являюсь поклонником многословных беглых интерфейсов, например, необходимости связывать воедино множественные вызовы атрибута («style», «display: none»). Attribute («class», «foo») и т. д. )

Если бы C # имел менее подробный синтаксис для литералов словаря, то я бы не стал включать этот синтаксис в компонент сетки:)

Я также хочу отметить, что использование этого в MvcContrib совершенно необязательно - это методы расширения, которые обертывают перегрузки, которые вместо этого принимают IDictionary. Я думаю, что важно, чтобы, если вы предоставляете такой метод, вы также поддерживали более «нормальный» подход, например, для взаимодействия с другими языками.

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

48 голосов
/ 12 ноября 2009

Я бы предпочел

Attributes.Add(string name, string value);

Это намного более явно и стандартно, и ничего не получается с помощью лямбды.

46 голосов
/ 12 ноября 2009

Welcome To Rails Land:)

В этом нет ничего плохого, если вы знаете, что происходит. (Это когда такая вещь плохо документирована, поэтому возникает проблема).

Вся структура Rails построена на идее соглашения о конфигурации. Обозначение вещей определенным образом вводит вас в соглашение, которое они используют, и вы получаете множество функциональных возможностей бесплатно. Следование соглашению об именах поможет вам двигаться быстрее. Все это прекрасно работает.

Еще одно место, где я видел подобную хитрость, это утверждения вызовов методов в Moq. Вы проходите в лямбду, но лямбда никогда не выполняется. Они просто используют выражение, чтобы убедиться, что вызов метода произошел, и выдают исключение, если нет.

42 голосов
/ 12 ноября 2009

Это ужасно на более чем одном уровне. И нет, это не что иное, как Руби. Это злоупотребление C # и .Net.

Было много предложений о том, как сделать это более простым способом: кортежи, анонимные типы, свободный интерфейс и так далее.

Что делает его таким плохим, так это то, что это просто способ придумать для себя:

  • Что происходит, когда вам нужно позвонить с VB?

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

  • Его полностью противоречащий интуитивному, intellisense мало поможет понять, как передать материал.

  • Это излишне неэффективно.

  • Никто не знает, как его поддерживать.

  • Какой тип аргумента используется для атрибутов, это Func<object,string>? Как это намерение раскрывается. В вашей документации на intellisense будет сказано: «Пожалуйста, не обращайте внимания на все значения объекта»

Я думаю, что вы полностью оправдываете чувство отвращения.

40 голосов
/ 12 ноября 2009

Я нахожусь в лагере "синтаксического блеска", если они четко это документируют, и это выглядит чертовски круто, то с этим почти нет проблем, imo!

37 голосов
/ 02 декабря 2009

Оба. Это злоупотребление лямбда-выражениями И Синтаксический блеск.

21 голосов
/ 12 ноября 2009

Я почти никогда не сталкивался с такого рода использованием. Я думаю, что это "неуместно":)

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

Против

  • Код не интуитивно понятен (обычные соглашения отличаются)
  • Он имеет тенденцию быть хрупким (переименование параметра нарушит функциональность).
  • Тестировать немного сложнее (для подделки API потребуется использование отражения в тестах).
  • Если выражение используется интенсивно, оно будет медленнее из-за необходимости анализировать параметр, а не только значение (стоимость отражения)

Плюсы

  • Это становится более читабельным после того, как разработчик настроен на этот синтаксис.

Итог - в публичном дизайне API я бы выбрал более явный путь.

18 голосов
/ 12 ноября 2009

Нет, это, конечно, не обычная практика. Это нелогично, просто посмотреть на код, чтобы понять, что он делает. Вы должны знать, как это используется, чтобы понять, как это используется.

Вместо предоставления атрибутов с использованием массива делегатов методы сцепления были бы более понятными и работали бы лучше:

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

Хотя это немного больше, чтобы напечатать, это ясно и интуитивно понятно.

...