Лямбды для обработчиков событий? - PullRequest
9 голосов
/ 21 декабря 2008

Лямбда-синтаксис в C # 3 делает действительно удобным создание однострочных анонимных методов. Это определенное улучшение по сравнению с более простым анонимным синтаксисом делегатов, который дал нам C # 2. Однако удобство лямбд приводит к искушению использовать их в тех местах, где нам необязательно нужна семантика функционального программирования, которую они предоставляют.

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

В этом сценарии есть несколько явных недостатков лямбды:

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

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

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

  • Каждая лямбда-функция формирует замыкание над своей содержащей областью. Это может означать, что временные объекты, созданные ранее в конструкторе, остаются живыми гораздо дольше, чем нужно из-за замыканий, поддерживающих ссылки на них. Теперь, надеюсь, компилятор достаточно умен, чтобы исключить объекты из замыкания, которые лямбда не использует, но я не уверен. Кто-нибудь знает?

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

  • Может пострадать ремонтопригодность. Долгое время. Если у меня есть некоторые обработчики событий, определенные как функции, а некоторые - как лямбды, я волнуюсь, что это может затруднить отслеживание ошибок или просто понимание класса. И позже, если и когда мои обработчики событий будут расширяться, мне придется либо переместить их в функции уровня класса, либо иметь дело с тем фактом, что мой конструктор теперь содержит значительный объем кода, который реализует функциональность моего класса .

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

Ответы [ 5 ]

4 голосов
/ 21 декабря 2008

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

  1. Распаковать параметры события.
  2. Вызовите именованный метод с соответствующими параметрами.

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

2 голосов
/ 21 декабря 2008

Основываясь на небольшом эксперименте с компилятором, я бы сказал, что компилятор достаточно умен, чтобы создать замыкание. То, что я сделал, было простым конструктором, который имел две разные лямбды, которые использовались для предиката в List.Find ().

Первая lamdba использовала жестко закодированное значение, вторая использовала параметр в конструкторе. Первая лямбда была реализована как закрытый статический метод в классе. Вторая лямбда была реализована как класс, который выполнял закрытие.

Итак, ваше предположение, что компилятор достаточно умен, правильно.

2 голосов
/ 21 декабря 2008

Каждая лямбда-функция образует замыкание над своей содержащей областью. Это может означать, что временные объекты, созданные ранее в конструкторе, остаются живыми гораздо дольше, чем нужно из-за замыканий, поддерживающих ссылки на них. Теперь, надеюсь, компилятор достаточно умен, чтобы исключить объекты из замыкания, которые лямбда не использует, но я не уверен. Кто-нибудь знает?

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

Другими словами, если вы не получите доступ к содержащей области изнутри вашей лямбды, это не сгенерирует замыкание.

Тем не менее, это немного "слухи", и я бы хотел, чтобы кто-то, кто более осведомлен о компиляторе C #, взвесил это.

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

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

1 голос
/ 21 декабря 2008

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

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

Я считаю, что это один из "хороших запахов" в смысле рефакторинга.

0 голосов
/ 15 апреля 2014

Что касается лямбд, этот вопрос Я недавно задавал несколько важных фактов о влиянии на продолжительность жизни объекта в принятом ответе.

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

Лично я не использую лямбды в качестве обработчиков событий, потому что чувствую, что преимущество читабельности действительно приходит, когда логика перетекает от запроса к результату. Обработчик событий, как правило, добавляется в конструктор или инициализатор, но он редко вызывается на этом этапе жизненного цикла объекта. Так почему мой конструктор должен читать так, как будто он делает то, что происходит на самом деле гораздо позже?

С другой стороны, я использую немного другой тип механизма событий в целом, который я считаю более предпочтительным, чем функция языка C #: NotificationCenter в стиле iOS, переписанный в C #, с таблицей диспетчеризации с ключом Type (производным от Notification ) и со значениями Action . Это позволяет использовать однострочные определения типа «событие», например:

public class UserIsUnhappy : Notification { public int unhappiness; }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...