Собственные идентификаторы класса F#

Все исходники / Язык программирования F# / OS Windows / Desktop / Исходники приложений / Собственные идентификаторы класса F#

Экземпляры класса

В языке 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() = "!"

Self Identifier для одного члена класса

Собственный идентификатор является обязательным при определении нестатического члена класса. С помощью данного идентификатора свойство или метод получают в своем программном коде доступ к другим нестатическим членам класса.

Пример использования собственного идентификатора с областью видимости только внутри метода:
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()

Исходник примеров применения собственных идентификаторов класса

Архивный файл исходника для тестирования примеров применения само-идентификаторов. Интегрированная среда MS Visual Studio 2022.