СДЕЛАЙТЕ СВОИ УРОКИ ЕЩЁ ЭФФЕКТИВНЕЕ, А ЖИЗНЬ СВОБОДНЕЕ

Благодаря готовым учебным материалам для работы в классе и дистанционно

Скидки до 50 % на комплекты
только до

Готовые ключевые этапы урока всегда будут у вас под рукой

Организационный момент

Проверка знаний

Объяснение материала

Закрепление изученного

Итоги урока

Лямбда-выражения. События

Категория: Информатика

Нажмите, чтобы узнать подробности

Просмотр содержимого документа
«Лямбда-выражения. События»

Лямбда-выражения. События

Лямбда-выражения. События

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

Лямбды

  • Лямбда-выражения представляют упрощенную запись анонимных методов. Лямбда-выражения позволяют создать емкие лаконичные методы, которые могут возвращать некоторое значение и которые можно передать в качестве параметров в другие методы.
  определяется список параметров, а справа блок выражений, использующий эти параметры: " width="640"

Ламбда-выражения имеют следующий синтаксис: слева от лямбда-оператора  =  

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

С точки зрения типа данных лямбда-выражение представляет делегат. Например, определим простейшее лямбда-выражение:
  • С точки зрения типа данных лямбда-выражение представляет делегат. Например, определим простейшее лямбда-выражение:
В данном случае переменная hello представляет делегат Message - то есть некоторое действие, которое ничего не возвращает и не принимает никаких параметров. В качестве значения этой переменной присваивается лямбда-выражение. Это лямбда-выражение должно соответствовать делегату Message - оно тоже не принимает никаких параметров, поэтому слева от лямбда-оператора идут пустые скобки. А справа от лямбда-оператора идет выполняемое выражение -  Console.WriteLine(

В данном случае переменная hello представляет делегат Message - то есть некоторое действие,

которое ничего не возвращает и не принимает никаких параметров. В качестве значения этой переменной

присваивается лямбда-выражение. Это лямбда-выражение должно соответствовать делегату Message - оно тоже не принимает никаких параметров, поэтому слева от лямбда-оператора идут пустые скобки. А справа от лямбда-оператора идет выполняемое выражение -  Console.WriteLine("Hello")

Затем в программе можно вызывать эту переменную как метод. Если лямбда-выражение содержит несколько действий, то они помещаются в фигурные скобки:
  • Затем в программе можно вызывать эту переменную как метод.
  • Если лямбда-выражение содержит несколько действий, то они помещаются в фигурные скобки:
Выше мы определили переменную hello, которая представляет делегат Message. Но начиная с версии  C# 10  мы можем применять неявную типизацию (определение переменной с помощью оператора  var ) при определении лямбда-выражения:
  • Выше мы определили переменную hello, которая представляет делегат Message. Но начиная с версии  C# 10  мы можем применять неявную типизацию (определение переменной с помощью оператора  var ) при определении лямбда-выражения:
Но какой тип в данном случае представляет переменная hello? При неявной типизации компилятор сам пытается сопоставить лямбда-выражение на основе его определения с каким-нибудь делегатом. Например, выше определенное лямбда-выражение hello по умолчанию компилятор будет рассматривать как переменную встроенного делегата  Action , который не принимает никаких параметров и ничего не возвращает.
  • Но какой тип в данном случае представляет переменная hello? При неявной типизации компилятор сам пытается сопоставить лямбда-выражение на основе его определения с каким-нибудь делегатом. Например, выше определенное лямбда-выражение hello по умолчанию компилятор будет рассматривать как переменную встроенного делегата  Action , который не принимает никаких параметров и ничего не возвращает.
Параметры лямбды При определении списка параметров мы можем не указывать для них тип данных :

Параметры лямбды

  • При определении списка параметров мы можем не указывать для них тип данных :
В данном случае компилятор видит, что лямбда-выражение sum представляет тип Operation, а значит оба параметра лямбды представляют тип  int . Поэтому никак проблем не возникнет.

В данном случае компилятор видит, что лямбда-выражение sum представляет тип Operation,

а значит оба параметра лямбды представляют тип  int . Поэтому никак проблем не возникнет.

Однако если мы применяем неявную типизацию, то у компилятора могут возникнуть трудности, чтобы вывести тип делегата для лямбда-выражения, например, в следующем случае
  • Однако если мы применяем неявную типизацию, то у компилятора могут возникнуть трудности, чтобы вывести тип делегата для лямбда-выражения, например, в следующем случае
В этом случае можно указать тип параметров
  • В этом случае можно указать тип параметров
Если лямбда имеет один параметр, для которого не требуется указывать тип данных, то скобки можно опустить :
  • Если лямбда имеет один параметр, для которого не требуется указывать тип данных, то скобки можно опустить :
Начиная с C# 12 параметры лямбда-выражений могут иметь значения по умолчанию:
  • Начиная с C# 12 параметры лямбда-выражений могут иметь значения по умолчанию:
Возвращение результата Лямбда-выражение может возвращать результат. Возвращаемый результат можно указать после лямбда-оператора :

Возвращение результата

  • Лямбда-выражение может возвращать результат. Возвращаемый результат можно указать после лямбда-оператора :
Если лямбда-выражение содержит несколько выражений (или одно выражение в фигурных скобках), тогда нужно использовать оператор  return , как в обычных методах :
  • Если лямбда-выражение содержит несколько выражений (или одно выражение в фигурных скобках), тогда нужно использовать оператор  return , как в обычных методах :
Добавление и удаление действий в лямбда-выражении Поскольку лямбда-выражение представляет делегат, тот как и в делегат, в переменную, которая представляет лямбда-выражение можно добавлять методы и другие лямбды:

Добавление и удаление действий в лямбда-выражении

  • Поскольку лямбда-выражение представляет делегат, тот как и в делегат, в переменную, которая представляет лямбда-выражение можно добавлять методы и другие лямбды:
Лямбда-выражение как аргумент метода Как и делегаты, лямбда-выражения можно передавать параметрам метода, которые представляют делегат :

Лямбда-выражение как аргумент метода

  • Как и делегаты, лямбда-выражения можно передавать параметрам метода, которые представляют делегат :
Метод Sum принимает в качестве параметра массив чисел и делегат IsEqual и возвращает сумму чисел массива в виде объекта int. В цикле проходим по всем числам и складываем их. Причем складываем только те числа, для которых делегат  IsEqual func  возвращает true. То есть делегат IsEqual здесь фактически задает условие, которому должны соответствовать значения массива. Но на момент написания метода Sum нам неизвестно, что это за условие.

Метод Sum принимает в качестве параметра массив чисел и делегат IsEqual и возвращает сумму чисел

массива в виде объекта int. В цикле проходим по всем числам и складываем их. Причем складываем только те числа,

для которых делегат  IsEqual func  возвращает true. То есть делегат IsEqual здесь фактически задает условие,

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

что это за условие.

При вызове метода Sum ему передается массив и лямбда-выражение:
  • При вызове метода Sum ему передается массив и лямбда-выражение:
То есть параметр x здесь будет представлять число, которое передается в делегат:
  • То есть параметр x здесь будет представлять число, которое передается в делегат:
5  представляет условие, которому должно соответствовать число. Если число соответствует этому условию, то лямбда-выражение возвращает true, а переданное число складывается с другими числами. " width="640"

А выражение  x 5  представляет условие, которому должно соответствовать число.

Если число соответствует этому условию, то лямбда-выражение возвращает true, а переданное число складывается

с другими числами.

Подобным образом работает второй вызов метода Sum, только здесь уже идет проверка числа на четность, то есть если остаток от деления на 2 равен нулю:
  • Подобным образом работает второй вызов метода Sum, только здесь уже идет проверка числа на четность, то есть если остаток от деления на 2 равен нулю:
Лямбда-выражение как результат метода Метод также может возвращать лямбда-выражение. В этом случае возвращаемым типом метода выступает делегат, которому соответствует возвращаемое лямбда-выражение. Например:

Лямбда-выражение как результат метода

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

События

  • События  сигнализируют системе о том, что произошло определенное действие. И если нам надо отследить эти действия, то как раз мы можем применять события.
Например, возьмем следующий класс, который описывает банковский счет:
  • Например, возьмем следующий класс, который описывает банковский счет:
В конструкторе устанавливаем начальную сумму, которая хранится в свойстве Sum. С помощью метода Put мы можем добавить средства на счет, а с помощью метода Take, наоборот, снять деньги со счета. Попробуем использовать класс в программе - создать счет, положить и снять с него деньги:
  • В конструкторе устанавливаем начальную сумму, которая хранится в свойстве Sum. С помощью метода Put мы можем добавить средства на счет, а с помощью метода Take, наоборот, снять деньги со счета. Попробуем использовать класс в программе - создать счет, положить и снять с него деньги:
Консольный вывод:
  • Консольный вывод:
Все операции работают как и положено. Но что если мы хотим уведомлять пользователя о результатах его операций. Мы могли бы, например, для этого изменить метод Put следующим образом:
  • Все операции работают как и положено. Но что если мы хотим уведомлять пользователя о результатах его операций. Мы могли бы, например, для этого изменить метод Put следующим образом:
Казалось, теперь мы будем извещены об операции, увидев соответствующее сообщение на консоли. Но тут есть ряд замечаний. На момент определения класса мы можем точно не знать, какое действие мы хотим произвести в методе Put в ответ на добавление денег. Это может вывод на консоль, а может быть мы захотим уведомить пользователя по email или sms. Более того мы можем создать отдельную библиотеку классов, которая будет содержать этот класс, и добавлять ее в другие проекты. 
  • Казалось, теперь мы будем извещены об операции, увидев соответствующее сообщение на консоли. Но тут есть ряд замечаний. На момент определения класса мы можем точно не знать, какое действие мы хотим произвести в методе Put в ответ на добавление денег. Это может вывод на консоль, а может быть мы захотим уведомить пользователя по email или sms. Более того мы можем создать отдельную библиотеку классов, которая будет содержать этот класс, и добавлять ее в другие проекты. 
И уже из этих проектов решать, какое действие должно выполняться. Возможно, мы захотим использовать класс Account в графическом приложении и выводить при добавлении на счет в графическом сообщении, а не консоль. Или нашу библиотеку классов будет использовать другой разработчик, у которого свое мнение, что именно делать при добавлении на счет. И все эти вопросы мы можем решить, используя события.
  • И уже из этих проектов решать, какое действие должно выполняться. Возможно, мы захотим использовать класс Account в графическом приложении и выводить при добавлении на счет в графическом сообщении, а не консоль. Или нашу библиотеку классов будет использовать другой разработчик, у которого свое мнение, что именно делать при добавлении на счет. И все эти вопросы мы можем решить, используя события.
Определение и вызов событий События объявляются в классе с помощью ключевого слова  event , после которого указывается тип делегата, который представляет событие :

Определение и вызов событий

  • События объявляются в классе с помощью ключевого слова  event , после которого указывается тип делегата, который представляет событие :
В данном случае вначале определяется делегат AccountHandler, который принимает один параметр типа string. Затем с помощью ключевого слова  event  определяется событие с именем Notify, которое представляет делегат AccountHandler. Название для события может быть произвольным, но в любом случае оно должно представлять некоторый делегат.
  • В данном случае вначале определяется делегат AccountHandler, который принимает один параметр типа string. Затем с помощью ключевого слова  event  определяется событие с именем Notify, которое представляет делегат AccountHandler. Название для события может быть произвольным, но в любом случае оно должно представлять некоторый делегат.
Определив событие, мы можем его вызвать в программе как метод, используя имя события:
  • Определив событие, мы можем его вызвать в программе как метод, используя имя события:
Поскольку событие Notify представляет делегат AccountHandler, который принимает один параметр типа string - строку, то при вызове события нам надо передать в него строку. Однако при вызове событий мы можем столкнуться с тем, что событие равно null в случае, если для его не определен обработчик. Поэтому при вызове события лучше его всегда проверять на null. Например, так:
  • Поскольку событие Notify представляет делегат AccountHandler, который принимает один параметр типа string - строку, то при вызове события нам надо передать в него строку.
  • Однако при вызове событий мы можем столкнуться с тем, что событие равно null в случае, если для его не определен обработчик. Поэтому при вызове события лучше его всегда проверять на null. Например, так:
Или так:
  • Или так:
В этом случае поскольку событие представляет делегат, то мы можем его вызвать с помощью метода  Invoke() , передав в него необходимые значения для параметров.
  • В этом случае поскольку событие представляет делегат, то мы можем его вызвать с помощью метода  Invoke() , передав в него необходимые значения для параметров.
Объединим все вместе и создадим и вызовем событие:
  • Объединим все вместе и создадим и вызовем событие:
Теперь с помощью события Notify мы уведомляем систему о том, что были добавлены средства и о том, что средства сняты со счета или на счете недостаточно средств.
  • Теперь с помощью события Notify мы уведомляем систему о том, что были добавлены средства и о том, что средства сняты со счета или на счете недостаточно средств.
Добавление обработчика события С событием может быть связан один или несколько обработчиков. Обработчики событий - это именно то, что выполняется при вызове событий. Нередко в качестве обработчиков событий применяются методы. Каждый обработчик событий по списку параметров и возвращаемому типу должен соответствовать делегату, который представляет событие. Для добавления обработчика события применяется операция  += :

Добавление обработчика события

  • С событием может быть связан один или несколько обработчиков. Обработчики событий - это именно то, что выполняется при вызове событий. Нередко в качестве обработчиков событий применяются методы. Каждый обработчик событий по списку параметров и возвращаемому типу должен соответствовать делегату, который представляет событие. Для добавления обработчика события применяется операция  += :
Определим обработчики для события Notify, чтобы получить в программе нужные уведомления:
  • Определим обработчики для события Notify, чтобы получить в программе нужные уведомления:
В данном случае в качестве обработчика используется метод DisplayMessage, который соответствует по списку параметров и возвращаемому типу делегату AccountHandler. В итоге при вызове события  Notify?.Invoke()   будет вызываться метод DisplayMessage, которому для параметра message будет передаваться строка, которая передается в  Notify?.Invoke() . В DisplayMessage просто выводим полученное от события сообщение, но можно было бы определить любую логику.

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

списку параметров и возвращаемому типу делегату AccountHandler. В итоге при вызове события  Notify?.Invoke()  

будет вызываться метод DisplayMessage, которому для параметра message будет передаваться строка, которая

передается в  Notify?.Invoke() . В DisplayMessage просто выводим полученное от события сообщение,

но можно было бы определить любую логику.

Если бы в данном случае обработчик не был бы установлен, то при вызове события  Notify?.Invoke()  ничего не происходило, так как событие Notify было бы равно null.  Консольный вывод программы: Теперь мы можем выделить класс Account в отдельную библиотеку классов и добавлять в любой проект.

Если бы в данном случае обработчик не был бы установлен, то при вызове события  Notify?.Invoke()  ничего не происходило, так как событие Notify было бы равно null.

Консольный вывод программы:

Теперь мы можем выделить класс Account в отдельную библиотеку классов и добавлять в любой проект.

Добавление и удаление обработчиков Для одного события можно установить несколько обработчиков и потом в любой момент времени их удалить. Для удаления обработчиков применяется операция  -= . Например:

Добавление и удаление обработчиков

  • Для одного события можно установить несколько обработчиков и потом в любой момент времени их удалить. Для удаления обработчиков применяется операция  -= . Например:
Консольный вывод:
  • Консольный вывод:
В качестве обработчиков могут использоваться не только обычные методы, но также делегаты, анонимные методы и лямбда-выражения. Использование делегатов и методов:
  • В качестве обработчиков могут использоваться не только обычные методы, но также делегаты, анонимные методы и лямбда-выражения. Использование делегатов и методов:
В данном случае разницы между двумя обработчиками никакой не будет. Установка в качестве обработчика анонимного метода:
  • В данном случае разницы между двумя обработчиками никакой не будет.
  • Установка в качестве обработчика анонимного метода:
Установка в качестве обработчика лямбда-выражения:
  • Установка в качестве обработчика лямбда-выражения:
Управление обработчиками С помощью специальных акссесоров  add/remove  мы можем управлять добавлением и удалением обработчиков. Как правило, подобная функциональность редко требуется, но тем не менее мы ее можем использовать. Например:

Управление обработчиками

  • С помощью специальных акссесоров  add/remove  мы можем управлять добавлением и удалением обработчиков. Как правило, подобная функциональность редко требуется, но тем не менее мы ее можем использовать. Например:
Теперь определение события разбивается на две части. Вначале просто определяется переменная делегата, через которую мы можем вызывать связанные обработчики:
  • Теперь определение события разбивается на две части. Вначале просто определяется переменная делегата, через которую мы можем вызывать связанные обработчики:
Во второй части определяем акссесоры add и remove. Аксессор  add  вызывается при добавлении обработчика, то есть при операции +=. Добавляемый обработчик доступен через ключевое слово  value . Здесь мы можем получить информацию об обработчике (например, имя метода через value.Method.Name) и определить некоторую логику. В данном случае для простоты просто выводится сообщение на консоль:
  • Во второй части определяем акссесоры add и remove. Аксессор  add  вызывается при добавлении обработчика, то есть при операции +=. Добавляемый обработчик доступен через ключевое слово  value . Здесь мы можем получить информацию об обработчике (например, имя метода через value.Method.Name) и определить некоторую логику. В данном случае для простоты просто выводится сообщение на консоль:
Блок remove вызывается при удалении обработчика. Аналогично здесь можно задать некоторую дополнительную логику:
  • Блок remove вызывается при удалении обработчика. Аналогично здесь можно задать некоторую дополнительную логику:
Внутри класса событие вызывается также через переменную notify. Но для добавления и удаления обработчиков в программе используется как раз Notify:
  • Внутри класса событие вызывается также через переменную notify. Но для добавления и удаления обработчиков в программе используется как раз Notify:
Консольный вывод программы:
  • Консольный вывод программы:
Передача данных события Нередко при возникновении события обработчику события требуется передать некоторую информацию о событии. Например, добавим и в нашу программу новый класс  AccountEventArgs  со следующим кодом:

Передача данных события

  • Нередко при возникновении события обработчику события требуется передать некоторую информацию о событии. Например, добавим и в нашу программу новый класс  AccountEventArgs  со следующим кодом:
Данный класс имеет два свойства: Message - для хранения выводимого сообщения и Sum - для хранения суммы, на которую изменился счет.
  • Данный класс имеет два свойства: Message - для хранения выводимого сообщения и Sum - для хранения суммы, на которую изменился счет.
Теперь применим класс AccoutEventArgs, изменив класс Account следующим образом:
  • Теперь применим класс AccoutEventArgs, изменив класс Account следующим образом:
По сравнению с предыдущей версией класса Account здесь изменилось только количество параметров у делегата и соответственно количество параметров при вызове события. Теперь делегат AccountHandler в качестве первого параметра принимает объект, который вызвал событие, то есть текущий объект Account. А в качестве второго параметра принимает объект AccountEventArgs, который хранит информацию о событии, получаемую через конструктор.
  • По сравнению с предыдущей версией класса Account здесь изменилось только количество параметров у делегата и соответственно количество параметров при вызове события. Теперь делегат AccountHandler в качестве первого параметра принимает объект, который вызвал событие, то есть текущий объект Account. А в качестве второго параметра принимает объект AccountEventArgs, который хранит информацию о событии, получаемую через конструктор.
Теперь изменим основную программу:
  • Теперь изменим основную программу:
По сравнению с предыдущим вариантом здесь мы только изменяем количество параметров и их использования в обработчике DisplayMessage. Благодаря первому параметру в методе можно получить информацию об отправителе события - счете, с которым производится операция. А через второй параметр можно получить инфомацию о состоянии операции.
  • По сравнению с предыдущим вариантом здесь мы только изменяем количество параметров и их использования в обработчике DisplayMessage. Благодаря первому параметру в методе можно получить информацию об отправителе события - счете, с которым производится операция. А через второй параметр можно получить инфомацию о состоянии операции.
Замыкания Замыкание  (closure) представляет объект функции, который запоминает свое лексическое окружение даже в том случае, когда она выполняется вне своей области видимости.

Замыкания

  • Замыкание  (closure) представляет объект функции, который запоминает свое лексическое окружение даже в том случае, когда она выполняется вне своей области видимости.
Технически замыкание включает три компонента: внешняя функция, которая определяет некоторую область видимости и в которой определены некоторые переменные и параметры - лексическое окружение переменные и параметры (лексическое окружение), которые определены во внешней функции вложенная функция, которая использует переменные и параметры внешней функции
  • Технически замыкание включает три компонента:
  • внешняя функция, которая определяет некоторую область видимости и в которой определены некоторые переменные и параметры - лексическое окружение
  • переменные и параметры (лексическое окружение), которые определены во внешней функции
  • вложенная функция, которая использует переменные и параметры внешней функции
В языке C# реализовать замыкания можно разными способами - с помощью локальных функций и лямбда-выражений.
  • В языке C# реализовать замыкания можно разными способами - с помощью локальных функций и лямбда-выражений.