Свойства

Последнее обновление: 11.12.2023

В типах в 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
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850