Speak.Me Учить иностранные слова

C#: лямбда выражения (Lambda Expressions)

Лямбда выражения — это безымянные методы, написанные вместо экземпляра делегата. При компиляции лямбда выражения преобразуются  либо в экземпляры делегата либо в дерево выражений, имеющее тип Expression<TDelegate> (это позволяет перевести код выражения в объект и интерпретировать его позже — во время выполнения).

В примере лямбда выражение x => x * x записывается в качестве экземпляра делегата Transformer. В подобной ситуации компилятор создает частный метод и перемещает код выражения в него.

Лямбда выражения имеют следующую форму:

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

В примере выше как раз используется один параметр x и выражение x * x:

Каждый параметр лямбда выражения соотносится с параметром делегата, а тип выражения (это может быть и void) соотносится с типом, возвращаемым делегатом. В примере выше x соотносится с параметром i, а выражение x * x соотносится с возвращаемым типом int и поэтому совместимо с делегатом Transform.

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

Лямбда выражения чаще всего используются с делегатами Func и Action:

Тип параметров в лямбда выражении компилятор как правило выводит автоматически, но можно и явно указать тип параметров:

Лямбда выражения могут принимать и несколько параметров:

Лямбда выражения могут использоваться и с событиями. Например, если предположить, Clicked — это событие типа EventHandler, то можно добавить обработчик с помощью лямбда выражения:

Захват внешних переменных

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

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

Захваченные переменные вычисляются не в момент захвата, а в момент, когда вызывается делегат:

Лямбда выражения сами могут обновлять захваченные переменные:

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

Захват переменных цикла

При захвате переменных цикла for, C# ведет себя с ними так, как будто они объявлены вне цикла. Поэтому на каждой интерации цикла захватывается одна и та же внешняя для цикла переменная. По этой причине в следующем примере мы получим 333, а не 012:

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

Если же нам нужно получить не 333, а 012, то единственным решением является присваивать значение переменной цикла на каждой интерации какой-либо локальной переменной, объявляемой внутри цикла, и захватывать уже эту локальную переменную:

Цикл foreach в предыдущих версиях языка работал также как цикл for, но начиная с версии C# 5.0 можно использовать переменные цикла foreach в замыканиях непосредственно, не создавая временных локальных переменных на каждой интерации.

Анонимные методы

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

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

Чтобы записать анонимный метод необходимо использовать ключевое слово delegate, после которого объявляются параметры (не обязательно) и в конце тело метода:

Тоже самое можно записать с помощью лямбда выражения:

Или еще короче:

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

Это избавляет от необходимости проверять делегат на null перед генерацией события. Тело метода не обязательно должно быть пустым. Например, следующее тоже допустимо:

Анонимные методы могут захватывать внешние переменные точно также как лямбда выражения.