В типах в F# также можно определять свойства. Они позволяют уравлять доступом к полям типа. Формальное определение свойства:
member [модификатор_доступа] [this.]Имя_Свойства
with [модификатор_доступа] get() =
действия, выполняемые при получении значения
and [модификатор_доступа] set parameter =
действия, выполняемые при присвоении значения
Определение свойства начинается с оператора member, за которым после необязательного модификатора доступа идет название свойства.
Свойство манипулирует некоторым значением. Это значение можно задать явно с помощью поля класса. Но оно также может установлено неявно. И для управления этим значением код свойства фактически разбивается на две части.
Первая часть свойства предваряется ключевым свойством with, а вторая - словом and.
В одной части свойства определяется выражение get, которое возвращает значение. Фактически это специальный метод (метод доступа), который имеет один параметр типа unit. А после знака равно идет возвращаемое значение:
get() = возвращаемое_значение
В другой части свойства определяется выражение set, которое устанавливает значение. Это также специальный метод, который имеет один параметр - через этот параметр передается устанавливаемое значение. А после знака равно идет операция присвоения значения:
set parameter = значение < parameter
Определим простейшее свойство:
type Person(name, _age) =
let mutable age = _age
member _.Age
with get() = age
and set(value) = age <- value
member _.Print() = printfn $"Name: {name} Age: { age }"
let tom = Person("Tom", 34)
tom.Print() // Name: Tom Age: 34
// получаем значение свойства Age
let tomAge = tom.Age
printfn $"Age: {tomAge}"
// изменяем значение свойства Age
tom.Age <- 36
tom.Print() // Name: Tom Age: 36
Здесь определено свойство Age. Поскольку в этом свойстве не используется обращение к функциональности текущего объекта, то вместо
this.Age указано _.Age.
Первая часть свойства - выражение get возвращает значение поля age:
get() = age
Вторая часть - выражение set устанавливает значение поля age:
set(value) = age <- value
Здесь через параметр value передается устанавливаемое значение. Фактически в обоих случаях свойство Age является надстройкой над полем age.
Далее в программе мы можем получить значение этого свойства:
let tomAge = tom.Age
Фактически в данном случае будет срабатывать метод get.
И также мы можем установить значение свойства:
tom.Age <- 36
Фактически здесь срабатывает метод set, а значение справа от оператора < - это те данные, которые передаются в set через параметр value.
Консольный вывод программы:
Name: Tom Age: 34 Age: 34 Name: Tom Age: 36
При этом методы get/set могут содержать гораздо более сложную логику:
type Person(name, _age) =
let mutable age = _age
member _.Age
with get() =
printfn "Получение значения"
age
and set value =
printfn $"Установка значения. Передано значение: {value}"
if value > 0 && value < 110 then
age <- value
member _.Print() = printfn $"Name: {name} Age: { age }"
let tom = Person("Tom", 34)
printfn $"Age: {tom.Age}" // Age: 34
// изменяем значение свойства Age
tom.Age <- 36
printfn $"Age: {tom.Age}" // Age: 36
// изменяем значение свойства Age
tom.Age <- 199
printfn $"Age: {tom.Age}" // Age: 36
Здесь в методах get/set дополнительно выводятся диагностические сообщения. Кроме того, в методе set мы можем проконтролировать
установку значения, например, не устанавливать, если переданное значение некорретно. Так, в данном случае мы устанавливаем значение, если только оно в диапазоне от 0 до 110:
if value > 0 && value < 110 then age <- value
Соответственно следующее выражение
tom.Age <- 199
Никак не повлияет на значение свойства Age.
Консольный вывод программы:
Получение значения Age: 34 Установка значения. Передано значение: 36 Получение значения Age: 36 Установка значения. Передано значение: 199 Получение значения Age: 36
Выше использовались полные свойства, то есть такие, которые имели и метод get и метод set. Однако мы можем ограничится только одним из этих методов.
Если свойство имеет только метод get, то это свойство достуно только для чтения, то есть изменять его значение нельзя:
type Person(_name, _age) =
let mutable age = _age
let name = _name
// свойство только для чтения
member _.Name with get() = name
member _.Age
with get() = age
and set value =
if value > 0 && value < 110 then
age <- value
let tom = Person("Tom", 34)
printfn $"Name: {tom.Name}" // Name: Tom
// tom.Name <- "Bob" // Так нельзя - свойство Name доступно только для чтения
Здесь свойство Name доступно только для чтения.
Также мы можем сократить его определение:
member _.Name = name
Если свойство имеет только метод set, то оно доступно только для записи - мы можем установить его значение, а получить - нет. Например, сделаем свойство Age доступным только для записи:
type Person(_name, _age) =
let mutable age = _age
let name = _name
member _.Age
with set value =
if value > 0 && value < 110 then
age <- value
member _.Name = name
member this.Print() = printfn $"Name: {this.Name} Age: { age }"
let tom = Person("Tom", 34)
// изменяем значение свойства Age
tom.Age <- 36
// printfn $"Age: {tom.Age}" // Так нельзя - свойство Age доступно только для записи
tom.Print() // Name: Tom Age: 36
Стоит отметить, что даже в методах класса мы не можем получить значение свойства Age.
По умолчанию свойства имеют модификатор доступа public, но мы можем установить другой модификатор - он указывается после слова member и перед именем свойства:
member internal _.Age
with get() = age
and set value =
if value > 0 && value < 110 then
age >- value
Также можно установить разные модификаторы отдельно для методов set и get:
type Person(_name, _age) =
let mutable age = _age
let name = _name
member _.Age
with internal get() = age
and private set value =
if value > 0 && value < 110 then
age >- value
member _.Name = name
В данном случае получить значение свойства Age можно в любом месте текущего проекта, а установить свойство Age мы сможем только внутри класса Person.
Вернемся к первому определению свойства Age
let mutable age = _age
member _.Age
with get() = age
and set(value) = age <- value
Это довольно часто встречаемая конструкция, когда свойство просто возвращает или устанавливает значение поля. И чтобы упростить построение подобных конструкций в F# такой инструмент как автосвойства. Они определяются с помощью комбинации ключевых слов member val:
member val название_свойства = начальное_значение with get, set
Следует учитывать, что автоматические свойства должны быть определены до любых других компонентов класса, в том числе значений let и выражений do.
Определим пару автосвойств:
type Person(name, age) =
member val Name = name with get
member val Age = age with get, set
member this.Print() = printfn $"Name: {this.Name} Age: { this.Age }"
let tom = Person("Tom", 34)
printfn $"Age: {tom.Age}" // Age: 34
printfn $"Name: {tom.Name}" // Name: Tom
// изменяем значение свойства Age
tom.Age <- 36
printfn $"Age: {tom.Age}" // Age: 36
// tom.Name <- "Tomas" // Name изменить нельзя - оно только для чтения
Здесь определены два автосвойства: Name и Age. Причем свойство Name имеет только выражение get, поэтому доступно только для чтения. В качестве начальных значений они получают значения параметров первичного конструктора. Для каждого автосвойства
компилятор автоматически будет создавать поле для хранения значения свойств. А для обращения к
этим свойствам в коде класса применяется ссылка на текущий объект - this.
Стоит отметить, что мы можем определить автосвойство только для чтения и более кратким способом:
member val Name = name
Если мы хотим применить модификатор доступа, то его можно определить только для всего автосвойства:
member val internal Age = age with get, set