Поскольку язык F# работает на платформе .NET, то он имеет полноценную поддержку событий (events). События сигнализируют системе о том, что произошло определенное действие. И если нам надо отследить эти действия, то как раз мы можем применять события.
Например, возьмем следующий класс, который описывает банковский счет:
type Account(sum)=
let mutable _sum = sum
member this.Sum
with get() = _sum
and private set v = _sum <- v
member this.Put(sum) = this.Sum <- this.Sum + sum
member this.Take(sum) =
if this.Sum >= sum then this.Sum <- this.Sum - sum
В конструкторе устанавливаем начальную сумму, которая хранится в свойстве Sum. С помощью метода Put мы можем добавить средства на счет, а с помощью метода Take, наоборот, снять деньги со счета. Попробуем использовать класс в программе - создать счет, положить и снять с него деньги:
let acc = Account(100) acc.Put 20 // добавляем на счет 20 printfn "Сумма на счете: %d" acc.Sum acc.Take 70 // пытаемся снять со счета 70 printfn "Сумма на счете: %d" acc.Sum acc.Take 180 // пытаемся снять со счета 180 printfn "Сумма на счете: %d" acc.Sum
Консольный вывод:
Сумма на счете: 120 Сумма на счете: 50 Сумма на счете: 50
Все операции работают как и положено. Но что если мы хотим уведомлять пользователя о результатах его операций. Мы могли бы, например, для этого изменить метод Put следующим образом:
member this.Put(sum) =
this.Sum <- this.Sum + sum
printfn "На счет поступило: %d" sum
Казалось, теперь мы будем извещены об операции, увидев соответствующее сообщение на консоли. Но тут есть ряд замечаний. На момент определения класса мы можем точно не знать, какое действие мы хотим произвести в методе Put в ответ на добавление денег. Это может вывод на консоль, а может быть мы захотим уведомить пользователя по email или sms. Более того мы можем создать отдельную библиотеку классов, которая будет содержать этот класс, и добавлять ее в другие проекты. И уже из этих проектов решать, какое действие должно выполняться. Возможно, мы захотим использовать класс Account в графическом приложении и выводить при добавлении на счет в графическом сообщении, а не консоль. Или нашу библиотеку классов будет использовать другой разработчик, у которого свое мнение, что именно делать при добавлении на счет. И все эти вопросы мы можем решить, используя события.
События объявляются в классе с помощью типа Event:
let _notify = Event<string>()
Здесь переменная _notify представляет событие. Тип Event типизируется типом, который представляет аргумент события - некоторые данные, которые мы хотим передать через событие во вне. В примере выше Event тпизирован типом string, соответственно через событие мы сможем передать строку. Но мы можем указать прочерк "_" для установки произвольного типа.
let _notify = Event<_>()
Для выова события определен метод Trigger(), в который можно передать аргумент события:
_notify.Trigger "Произошло действие"
Поскольку событие _notify принимает один параметр типа string - строку, то при вызове события через метод Trigger нам надо передать в него строку.
Другой аспект - переменная _notify может быть неизменяемой и недоступна извне, но нам надо как-то известить код извне о том, что внутри класса произошло некоторое событие. Для этого применяется метод Publish(). И частая практика заключается в том, чтобы определить свойство, через которое внешний код может прикрепить обработчик к событию:
member this.Notify = _notify.Publish
Объединим все вместе и создадим и вызовем событие:
type Account(sum)=
let mutable _sum = sum
let _notify = Event<string>() // 1.Определение события
member this.Notify = _notify.Publish
member this.Sum
with get() = _sum
and private set v = _sum <- v
member this.Put(sum) =
this.Sum <- this.Sum + sum
_notify.Trigger $"На счет поступило: {sum}" // 2.Вызов события
member this.Take(sum) =
if this.Sum >= sum then
this.Sum <- this.Sum - sum
_notify.Trigger $"Со счета снято: {sum}" // 2.Вызов события
else
_notify.Trigger $"Недостаточно денег на счете. Текущий баланс: {sum}" // 2.Вызов события
let display message = printfn "%s" message
let acc = Account(100)
acc.Notify.Add display // 3. Добавляем обработчик для события Notify
// можно вместо функции добавить лямбду
//acc.Notify.Add (fun msg -> display msg)
acc.Put 20
printfn "Сумма на счете: %d" acc.Sum
acc.Take 70
printfn "Сумма на счете: %d" acc.Sum
acc.Take 180 // пытаемся снять со счета 180
printfn "Сумма на счете: %d" acc.Sum
Для добавления действия, которое будет вызываться при добавлении события, применяется метод Add():
acc.Notify.Add display
Здесь в качестве обработчика события Notify применяется функция display. Она соответствует событию, так как принимает один аргумент - строку.
Теперь с помощью события Notify мы уведомляем систему о том, что были добавлены средства и о том, что средства сняты со счета или на счете недостаточно средств.
Консольный вывод программы:
На счет поступило: 20 Сумма на счете: 120 Со счета снято: 70 Сумма на счете: 50 Недостаточно денег на счете. Текущий баланс: 50 Сумма на счете: 50
Теперь мы можем выделить класс Account в отдельную библиотеку классов и добавлять в любой проект.