Отражающее имя параметра:злоупотребление лямбда-выражениями C# или великолепием синтаксиса?

StackOverflow https://stackoverflow.com/questions/1718037

Вопрос

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

Это было полезно?

Решение

У этого плохое взаимодействие.Например, рассмотрим этот пример C# - F#.

С#:

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

Ф#:

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

Результат:

печатается «arg» (а не «yadda»).

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

Attributes.Add(string name, string value);

Это гораздо более явно и стандартно, и использование лямбда-выражений ничего не дает.

Добро пожаловать в Rails Land :)

На самом деле в этом нет ничего плохого, если вы знаете, что происходит.(Проблема возникает тогда, когда подобные вещи не документированы должным образом).

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

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

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

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

Что делает его таким плохим, так это то, что это всего лишь способ фантазировать себе во благо:

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

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

  • Совершенно нелогично, Intellisense мало поможет понять, как передавать данные.

  • Это неоправданно неэффективно.

  • Никто не будет иметь ни малейшего понятия, как его поддерживать.

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

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

Я принадлежу к лагерю «блестящего синтаксиса», если они это четко документируют и это выглядит чертовски круто, то, по моему мнению, с этим почти нет проблем!

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

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

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

Минусы

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

Плюсы

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

Нижняя граница - при проектировании публичного API я бы выбрал более явный путь.

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

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

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

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

Могу ли я использовать это, чтобы придумать фразу?

магическая лямбда (n):лямбда-функция, используемая исключительно для замены магической строки.

Что не так со следующим:

html.Attributes["style"] = "width:100%";

Все эти разглагольствования об «ужасности» — это слишком чрезмерная реакция группы ребят со стажем работы в C# (а я давний программист C# и до сих пор очень большой поклонник этого языка).В этом синтаксисе нет ничего ужасного.Это просто попытка сделать синтаксис более похожим на то, что вы пытаетесь выразить.Чем меньше «шума» в синтаксисе чего-либо, тем легче программисту это понять.Уменьшение шума в одной строке кода помогает лишь немного, но если он будет распространяться на все больше и больше кода, то это окажется существенным преимуществом.

Это попытка автора добиться тех же преимуществ, которые дает вам DSL — когда код просто «выглядит» так, как вы пытаетесь сказать, вы попали в волшебное место.Вы можете спорить, хорошо ли это для взаимодействия или достаточно ли это лучше, чем анонимные методы, чтобы оправдать некоторые затраты на «сложность».Справедливо ...поэтому в вашем проекте вы должны сделать правильный выбор, использовать ли такой синтаксис.Но все равно ...это умная попытка программиста сделать то, что, в конце концов, мы все пытаемся сделать (понимаем мы это или нет).И то, что мы все пытаемся сделать, это:«Скажите компьютеру, что мы хотим, чтобы он делал, на языке, максимально близком к тому, как мы думаем о том, что он хочет».

Приближение к тому, чтобы выражать наши инструкции для компьютеров так, как мы думаем внутри компании, является ключом к тому, чтобы сделать программное обеспечение более удобным в сопровождении и более точным.

РЕДАКТИРОВАТЬ:Я сказал «ключ к тому, чтобы сделать программное обеспечение более удобным в обслуживании и более точным», что является безумно наивным и преувеличенным единорогом.Я изменил его на «ключ».

Это одно из преимуществ деревьев выражений — можно изучить сам код на наличие дополнительной информации.Вот как .Where(e => e.Name == "Jamie") может быть преобразовано в эквивалентное предложение SQL Where.Это умное использование деревьев выражений, хотя я надеюсь, что оно не зайдет дальше этого.Что-то более сложное, вероятно, будет сложнее, чем код, который оно надеется заменить, поэтому я подозреваю, что оно будет самоограничивающимся.

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

Expression<Func<object, string>>

Я думаю, что вы действительно хотите, а не делегат (вы используете Lambda, чтобы получить имена обеих сторон) см. Наивную реализацию ниже:

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;
}

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

Код очень умный, но потенциально он вызывает больше проблем, чем решает.

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

Лучшим решением было бы иметь элемент данных, который можно было бы проверить во время компиляции.Итак, вместо этого:

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

код со свойством Style может быть проверен компилятором:

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

или даже:

.Attributes.Style.Width.Percent = 100;

Это требует дополнительной работы для авторов кода, но этот подход использует преимущества сильной возможности проверки типов C#, которая помогает предотвратить попадание ошибок в код.

действительно, это похоже на Ruby =), по крайней мере, для меня использование статического ресурса для последующего динамического «поиска» не соответствует соображениям дизайна API, надеюсь, что этот умный трюк не является обязательным в этом API.

Мы могли бы наследовать IDictionary (или нет) и предоставить индексатор, который ведет себя как массив PHP, когда вам не нужно добавлять ключ для установки значения.Это будет правильное использование семантики .net, а не только C#, и для него все еще потребуется документация.

надеюсь это поможет

ИМХО, это крутой способ сделать это.Нам всем нравится тот факт, что присвоение имени классу Controller сделает его контроллером в MVC, верно?Поэтому бывают случаи, когда именование имеет значение.

Кроме того, намерение здесь очень ясно.Это очень легко понять, .Attribute( book => "something") приведет к book="something" и .Attribute( log => "something") приведет к log="something"

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

На мой взгляд это злоупотребление лямбдами.

Что касается синтаксического блеска, я нахожу style=>"width:100%" просто сбивает с толку.В частности из-за => вместо =

Если имена методов (функций) выбраны правильно, то это отличный способ избежать головной боли при обслуживании (т. е.:добавил новую функцию, но забыл добавить ее в список сопоставления параметров функции).Конечно, вам нужно тщательно это документировать, и вам лучше автоматически генерировать документацию для параметров из документации для функций в этом классе...

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top