В языке F# собственный идентификатор (self-identifier, идентификатор экземпляра, само-идентификатор) — это имя, представляющее текущий экземпляр класса. Абстрактные примеры экземпляров одного типа класса: общий тип Собака - экземпляры: Дружок, Полкан, Барбос; общий тип Планета - экземпляры: Земля, Марс, Венера.
Примеры создания экземпляров класса:
// Определение типа
type MyClass(number, name) =
let count = number
let title = name
// Создание экземпляров типа
let myclass1 = MyClass(5, "Первый")
let myclass2 = MyClass(10, "Второй")
let myclass3 = MyClass(15, "Третий")
Собственные идентификаторы необходимы для доступа к членам текущего экземпляра класса . Имя идентификаторам экземпляров класса можно присвоить любое, как правило краткое. В языке F# определено два вида собственных идентификаторов с различной областью видимости: для всего определения класса и только для одного члена класса.
Собственные идентификаторы F# похожи на ссылку this текущего экземпляра в языках C#, Kotlin, на скрытый указатель this в классах С++, PHP, на ключевое слово self в языке Python и т.п.
Статические члены класса F# не требуют собственных идентификаторов, поскольку относятся ко всему типу класса, но к отдельному экземпляру.
Self-identifier с областью видимости для всего определения класса объявляется ключевым словом as, следующим после круглых скобок конструктора. Собственный идентификатор для всего класса можно использовать как в конструкторах, так и в теле любого члена класса. Данный идентификатор является опциональным и нет требований определять его обязательно.
Идентификатор экземпляра определяет класс как рекурсивный, и если объявить self-identifier без его использования это вызовет дополнительную проверку во время выполнения при инициализации типов. Язык F# дисциплинирует, но в тоже время разрешает многое, поэтому компилятор при неиспользованном собственном идентификаторе для всего класса выдаст предупреждение, но не ошибку. Использовать self-identifier после первичного конструктора рекомендуется только тогда, когда они действительно необходимы.
Использовать само-идентификаторы в теле первичного конструктора можно только в do-привязках идущих после всех let-привязок первичного конструктора. Собственный идентификатор внутри let-привязки первичного конструктора вызовет исключение: System.InvalidOperationException: "Инициализация объекта или значения привела к рекурсивному вызову объекта или значения перед его полной инициализацией." Полная инициализация само-идентификатора происходит после выполнения всех let-привязок первичного конструктора.
Само-идентификаторы класса связаны с конструкторами. Для получения доступа к членам класса из дополнительных конструкторов необходимо определить собственный идентификатор для каждого конструктора отдельно.
Примеры программного кода определения собственных идентификаторов с видимостью для всего класса:
type MyClass() as self =
let space = " "
do self.M()
// Если раскомментировать эту строчку возникнет исключение:
// System.InvalidOperationException
//let add = 0
// Само-идентификатор для второго конструктора.
new(number: int) as self2 =
MyClass()
then
self2.M()
new(name: string) as self3 =
MyClass()
then self3.M()
member x.M() =
printfn "%s" (x.M1() + space + x.M2() + x.M3())
member x.M1() = "Hello"
member x.M2() = "World"
member x.M3() = "!"
Собственный идентификатор является обязательным при определении нестатического члена класса. С помощью данного идентификатора свойство или метод получают в своем программном коде доступ к другим нестатическим членам класса.
Пример использования собственного идентификатора с областью видимости только внутри метода:
type MyClass() =
let space = " "
member x.M() =
printfn "%s" (x.M1() + space + x.M2() + x.M3())
member private x.M1() = "Hello"
member private x.M2() = "World"
member private x.M3() = "!"
В случаях, когда само-идентификатор никогда не используется, традиционно принято использовать символ подчеркивания:
type Class() =
member _.Method() = printfn "Method()"
Примеры применения само-идентификаторов для классов, свойств и методов Исходник примеров прикреплён внизу страницы.
// ===== Собственные идентификаторы экземпляра класса F# =====
open System
type MyClass(strdate: string) as classId =
let mutable property = DateTime.Today
let safeConvertData (str: string) : DateTime =
try
// Пытаемся конвертировать строку в дату.
System.DateTime.Parse(str)
// В случае исключения примем меры.
with
| ex ->
printfn $"Не корректная дата!"
// Изменение даты на прошлую не будет выводить
// показания остатка дней до события.
DateTime.Today.AddDays(-1)
do
// Само-идентификатору экземпляра класса доступны любые члены:
// закрытые и открытые.
classId.StringDate <- strdate
classId.Date <- DateTime.Today.AddDays(1)
classId.GetDaysBeforeDate()
// Доступ невозможен! Области действия идентификаторов методов
// только внутри члена класса
//w.StringDate <- strdate
//x.GetBeforeDate()
// Если раскомментировать эту строку
// возникнет исключение:
// System.InvalidOperationException:
// "Инициализация объекта или значения привела к рекурсивному
// вызову объекта или значения перед его полной инициализацией."
// do-привязка с использованием собственного идентификатора
// класса должна быть последней в определении
// первичного конструктора.
//let lastinit = 67
// Собственный идентификатор метода необходим методу для доступа к другим членам класса.
member m.StringDate
with get () = property.ToLongDateString()
and set (value) = property <- safeConvertData (value)
member q.Date
with private get () = property
and private set (value) = property <- value
member x.GetDaysBeforeDate() =
let today = DateTime.Today
// x.Date доступ к члену класса через само-идентификатор метода.
let before = (x.Date - today).Days
if before >= 0 then
// classId.StringDate доступ к члену класса через само-идентификатор класса.
printfn $"Осталось дней - %d{before} до даты %s{classId.StringDate}"
// Исполнение кода.
let id = MyClass("23 ноября 2021")
printfn $""
id.StringDate <- "2021.11.22"
id.GetDaysBeforeDate()
printfn $""
id.StringDate <- "2021.11.56"
id.GetDaysBeforeDate()