Классы (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
разделе описываются необязательные параметры универсального типа. Он состоит из имен параметров типа и ограничений, заключенных в угловые скобки (<
и >
). Дополнительные сведения см. в разделе "Универсальные" и "Ограничения". Описывает parameter-list
параметры конструктора. Первый модификатор доступа относится к типу; Второй относится к основному конструктору. В обоих случаях используется public
значение по умолчанию.
Базовый класс для класса указывается с помощью inherit
ключевое слово. Для конструктора базового класса необходимо указать аргументы в скобках.
Вы объявляете поля или значения функций, которые являются локальными для класса с помощью let
привязок, и необходимо следовать общим правилам привязки let
. В do-bindings
этом разделе содержится код, выполняемый при построении объекта.
Состоит member-list
из дополнительных конструкторов, объявлений экземпляров и статических методов, объявлений интерфейса, абстрактных привязок и объявлений свойств и событий. Они описаны в разделе "Члены".
Объект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)
разрешить и выполнять привязки
do
Привязки let
в определении класса формируют текст конструктора основного класса и поэтому выполняются при создании экземпляра класса. Если привязка 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
идентификатор, объявленный с помощью ключевое слово, не инициализирован до тех пор, пока базовый конструктор не будет инициализирован. Поэтому при использовании до или внутри базового конструктора System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized.
будет возникать во время выполнения. Вы можете свободно использовать идентификатор самостоятельно после базового конструктора, например в 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#разрешен только один прямой базовый класс. Интерфейсы, реализующие класс, не считаются базовыми классами. Интерфейсы рассматриваются в разделе "Интерфейсы ".
Вы можете получить доступ к методам и свойствам базового класса из производного класса с помощью языка ключевое слово base
в качестве идентификатора, за которым следует точка (.) и имя члена.
Дополнительные сведения см. в разделе Наследование.
Раздел "Члены"
В этом разделе можно определить статические или экземплярные методы, свойства, реализации интерфейса, абстрактные члены, объявления событий и дополнительные конструкторы. Разрешить и выполнять привязки не могут отображаться в этом разделе. Так как члены могут добавляться в различные типы 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 или другой объектно-ориентированной библиотекой, особенно если требуется расшириться от объектно-ориентированной системы типов, например библиотеки пользовательского интерфейса, классы, вероятно, подходят.
Если вы не взаимодействуете тесно с объектно-ориентированным кодом или если вы пишете код, автономный и поэтому защищенный от частого взаимодействия с объектно-ориентированным кодом, следует рассмотреть возможность использования сочетания классов, записей и дискриминированных профсоюзов. Одно, хорошо продуманное объединение, вместе с соответствующим кодом сопоставления шаблонов, часто можно использовать в качестве более простой альтернативы иерархии объектов. Дополнительные сведения о дискриминированных профсоюзах см. в разделе " Дискриминированные профсоюзы".
Записи имеют преимущество быть проще, чем классы, но записи не подходят, если требования типа превышают то, что можно сделать с их простотой. Записи — это простое статистическое выражение значений без отдельных конструкторов, которые могут выполнять пользовательские действия без скрытых полей и без реализации наследования или интерфейса. Хотя элементы, такие как свойства и методы, можно добавить в записи, чтобы сделать их поведение более сложными, поля, хранящиеся в записи, по-прежнему являются простым агрегатом значений. Дополнительные сведения о записях см. в разделе "Записи".
Структуры также полезны для небольших агрегатов данных, но они отличаются от классов и записей в том, что они являются типами значений .NET. Классы и записи — это ссылочные типы .NET. Семантика типов значений и ссылочных типов отличаются в том, что типы значений передаются по значению. Это означает, что они копируют бит для бита, когда они передаются в качестве параметра или возвращаются из функции. Они также хранятся в стеке или, если они используются в качестве поля, внедренные внутри родительского объекта, а не хранятся в отдельном расположении в куче. Поэтому структуры подходят для часто доступ к данным, когда затраты на доступ к куче являются проблемой. Дополнительные сведения о структурах см. в разделе "Структуры".