Классы (F#)
Классы — это типы, представляющие объекты, которые могут иметь свойства, методы и события.
// Class definition:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
[ class ]
[ inherit base-type-name(base-constructor-args) ]
[ let-bindings ]
[ do-bindings ]
member-list
...
[ end ]
// Mutually recursive class definitions:
type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...
Заметки
Классы представляют фундаментальное описание типов объектов .NET. Класс — основная реализация понятия типа с поддержкой объектно-ориентированного программирования в F#.
В указанном выше синтаксисе type-name — любой допустимый идентификатор.type-params — необязательные параметры универсального типа.Указываются имена типов параметров и ограничения, заключенные в угловые скобки (< и >).Дополнительные сведения см. в разделах Универсальные шаблоны (F#) и Ограничения (F#).parameter-list — параметры конструктора.Первый модификатор доступа относится к типу; Второй относится к основному конструктору.В обоих случаях значение по умолчанию — public.
Базовый класс для класса указывается с помощью ключевого слова inherit.В скобках необходимо указать аргументы для конструктора базового класса.
Локальные поля и имена функций класса объявляются с помощью привязок let, при этом необходимо соблюдать общие правила по привязкам let.В разделе do-bindings содержится код, выполняемый при создании объекта.
В разделе member-list содержатся дополнительные конструкторы, объявления статических методов и методов экземпляров, объявления интерфейса, абстрактные привязки и объявления свойств и событий.Их описание приводится в разделе Члены (F#).
Идентификатор identifier, используемый с ключевым словом as (которое можно опустить), указывает имя переменной экземпляра, или собственный идентификатор, который можно использовать в определении типа для указания экземпляра этого типа.Дополнительные сведения см. в подразделе "Собственные идентификаторы" далее в этом разделе.
Ключевые слова class и end, отмечающие начало и конец определения, можно опустить.
Взаимно рекурсивные типы, т. е. типы, ссылающиеся друг на друга, объединяются с помощью ключевого слова and, как и взаимно рекурсивные функции.Пример см. в подразделе "Взаимно рекурсивные типы".
Конструкторы
Конструктор — это код, обеспечивающий создание экземпляра типа класса.Конструкторы классов в языке F# действуют не совсем так, как в других языках .NET.В классах F# всегда имеется главный конструктор, аргументы которого описаны в разделе parameter-list после имени типа, и его основная часть состоит из привязок let (и let rec) в начале объявления класса и следующих за ними привязок do.Аргументы главного конструктора находятся в области действия во всем определении класса.
Можно добавлять дополнительные конструкторы с помощью ключевого слова new, добавляющего дополнительные члены:
new(argument-list) = constructor-body
В основной части нового конструктора должен быть вызов главного конструктора, указанного в верхней части объявления класса.
Этот принцип показан в приведенном ниже примере.В следующем коде класс MyClass имеет два конструктора: главный конструктор, принимающий два аргумента, и другой конструктор, не принимающий аргументов.
type MyClass1(x: int, y: int) =
do printfn "%d %d" x y
new() = MyClass1(0, 0)
Привязки let и do
Привязки let и do в определении класса образуют основную часть главного конструктора, поэтому они выполняются при создании каждого экземпляра класса.Если привязка let является функцией, то она компилируется в член.Если привязка let является значением, не используемым никакой функцией или членом, то она компилируется в переменную, локальную для конструктора.В противном случае она компилируется в поле класса.Последующие выражения do компилируются в главный конструктор и выполняют код инициализации для каждого экземпляра.Поскольку все дополнительные конструкторы всегда вызывают главный конструктор, привязки let и do всегда выполняются, вне зависимости от того, какой именно конструктор вызван.
К полям, созданным с помощью привязок let, можно обращаться через методы и свойства класса; впрочем, к ним нельзя обращаться через статические методы, даже если статический метод принимает переменную экземпляра в качестве параметра.К ним нельзя обращаться через собственный идентификатор, если он и существует.
Собственные идентификаторы
Собственный идентификатор — это имя, представляющее текущий экземпляр.Собственные идентификаторы подобны ключевому слову this в языках C# и C++ и ключевому слову Me в языке Visual Basic.Собственный идентификатор можно объявить двумя различными способами, в зависимости от его требуемой области действия: для всего определения класса или только для отдельного метода.
Чтобы определить собственный идентификатор для всего класса, укажите ключевое слово as после закрывающей скобки списка параметров конструктора и укажите имя идентификатора.
Чтобы определить собственный идентификатор только для одного метода, укажите собственный идентификатор в объявлении метода прямо перед именем метода, используя точку (.) в качестве разделителя.
В следующем примере кода демонстрируются два способа создания собственного идентификатора.В первой строке собственный идентификатор определяется с помощью ключевого слова as.В пятой строке с помощью идентификатора this определяется собственный идентификатор, область действия которого ограничена методом PrintMessage.
type MyClass2(dataIn) as self =
let data = dataIn
do
self.PrintMessage()
member this.PrintMessage() =
printf "Creating MyClass2 with Data %d" data
В отличие от других языков .NET, собственному идентификатору можно задать любое имя: выбор не ограничен такими именами, как self, Me или this.
Собственный идентификатор, объявленный с помощью ключевого слова as, не инициализируется до выполнения привязок let.Поэтому его нельзя использовать в привязках let.Собственный идентификатор можно использовать в разделе привязок do.
Параметры универсального типа
Параметры универсального типа указываются в угловых скобках (< и >) в следующей форме: одинарная кавычка, затем идентификатор.Параметры универсального типа разделяются запятыми.Областью действия параметра универсального типа является все объявление.В следующем примере кода демонстрируется указание параметров универсального типа.
type MyGenericClass<'a> (x: 'a) =
do printfn "%A" x
Типы аргументов определяются при использовании типов.В следующем коде определяемый тип является последовательностью кортежей.
let g1 = MyGenericClass( seq { for i in 1 .. 10 -> (i, i*i) } )
Указание наследования
Ключевое слово inherit определяет прямой базовый класс, если он имеется.В языке F# допускается только один базовый класс.Интерфейсы, реализуемые классом, не считаются базовыми классами.Сведения об интерфейсах приведены в разделе Интерфейсы (F#).
К методам и свойствам базового класса можно обращаться через производный класс, указав в качестве идентификатора ключевое слово языка base, за которым следует точка (.) и имя элемента.
Дополнительные сведения см. в разделе Наследование (F#).
Раздел членов
В этом разделе можно определять статические методы и методы экземпляров, свойства, реализации интерфейсов, абстрактные члены, объявления событий и дополнительные конструкторы.Привязки let и do не могут содержаться в этом разделе.Поскольку в языке F# существует множество типов и классов, в которые можно добавлять члены, они рассматриваются в отдельном разделе Члены (F#).
Взаимно рекурсивные типы
При определении типов, ссылающихся друг на друга циклически, эти определения соединяются ключевым словом and.Ключевое слово and заменяет слово type во всех определениях, кроме самого первого, как показано в следующем коде.
open System.IO
type Folder(pathIn: string) =
let path = pathIn
let filenameArray : string array = Directory.GetFiles(path)
member this.FileArray = Array.map (fun elem -> new File(elem, this)) filenameArray
and File(filename: string, containingFolder: Folder) =
member this.Name = filename
member this.ContainingFolder = containingFolder
let folder1 = new Folder(".")
for file in folder1.FileArray do
printfn "%s" file.Name
Этот код выводит список всех файлов в текущем каталоге.
Когда использовать классы, объединения, записи и структуры
Существует множество различных типов, и необходимо четко представлять, для чего предназначен каждый из них, чтобы выбрать надлежащий тип для определенной ситуации.Классы предназначены для использования в объектно-ориентированной среде.Объектно-ориентированное программирование — доминирующий принцип написания кода в приложениях для .NET Framework.При разработке кода F#, интенсивно использующего платформу .NET Framework или другую объектно-ориентированную библиотеку, в особенности если в качестве основы используется объектно-ориентированная система типов, такая как библиотека пользовательского интерфейса, как правило, рекомендуется использовать классы.
При отсутствии тесного взаимодействия с объектно-ориентированным кодом или при написании самодостаточного когда (и, следовательно, защищенного от частого взаимодействия с объектно-ориентированным кодом), рассмотрите возможность использования записей и размеченных объединений.Одно тщательно проработанное размеченное объединение в сочетании с соответствующим кодом для сопоставления с шаблонами часто оказывается более простой альтернативой иерархии объектов.Дополнительные сведения о размеченных объединениях см. в разделе Размеченные объединения (F#).
Преимущество записей заключается в их простоте по сравнению с классами, но они неуместны в ситуациях, когда требования к типу превосходят ограничения, накладываемые их простотой.Грубо говоря, записи — это простые агрегаты значений без отдельных конструкторов, способные выполнять настраиваемые действия, без скрытых полей и без реализации наследования и интерфейсов.Несмотря на возможность усложнения поведения записей за счет добавления таких членов, как свойства и методы, поля записей остаются простыми агрегатами значений.Дополнительные сведения о записях см. в разделе Записи (F#).
Конструкции также полезны для мелкомасштабного агрегирования данных, но их отличие от классов и записей заключается в том, что они представляют собой типы значений .NET.Классы и записи являются ссылочными типами .NET.Семантика типов значений и ссылочных типов различается в том, что типы значений передаются по значению.Это означает, что они копируются побитово при их передаче в виде параметров или возврате функцией.Кроме того, они хранятся в стеке или (при использовании в качестве полей) встраиваются в родительский объект, а не в отдельном расположении в куче.Поэтому конструкции уместно использовать для данных, доступ к которым осуществляется часто, когда дополнительные затраты ресурсов на доступ к куче нежелательны.Дополнительные сведения о конструкциях см. в разделе Структуры (F#).