Классы (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 ...
...

Remarks

Классы представляют фундаментальное описание типов объектов .NET. класс является концепцией основного типа, поддерживающей объектно-ориентированное программирование в F #.

В приведенном выше синтаксисе type-name — любой допустимый идентификатор. type-paramsОписывает необязательные параметры универсального типа. Он состоит из имен параметров типа и ограничений, заключенных в угловые скобки ( < и > ). Дополнительные сведения см. в разделе универсальные шаблоны и ограничения. parameter-listОписывает параметры конструктора. Первый модификатор доступа относится к типу; второй объект относится к основному конструктору. В обоих случаях значение по умолчанию — public .

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

Поля или значения функций, локальные для класса, объявляются с помощью let привязок, поэтому необходимо следовать общим правилам для let привязок. do-bindingsРаздел содержит код, выполняемый при создании объекта.

member-listСодержит дополнительные конструкторы, объявления экземпляров и статических методов, объявления интерфейсов, абстрактные привязки и объявления свойств и событий. Они описаны в разделе элементы.

Объект identifier , используемый с as ключевым словом Optional, задает имя переменной экземпляра или собственный идентификатор, который можно использовать в определении типа для ссылки на экземпляр типа. Дополнительные сведения см. в подразделе «собственные идентификаторы» далее в этом разделе.

Ключевые слова 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 ключевым словом, не инициализируется до тех пор, пока не будет создан базовый конструктор. Поэтому при использовании до или в базовом конструкторе 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 # допускается только один прямой базовый класс. Интерфейсы, реализуемые классом, не считаются базовыми классами. Интерфейсы обсуждаются в разделе интерфейсы .

Доступ к методам и свойствам базового класса из производного класса можно получить с помощью ключевого слова Language в base качестве идентификатора, за которым следует точка (.) и имя члена.

Дополнительные сведения см. в разделе Наследование.

Раздел членов

В этом разделе можно определить статические методы или экземпляры методов, свойства, реализации интерфейса, абстрактные элементы, объявления событий и дополнительные конструкторы. Привязки let и Do не могут присутствовать в этом разделе. Поскольку члены могут добавляться в различные типы 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. Семантика типов значений и ссылочных типов различается в том, что типы значений передаются по значению. Это означает, что они копируются бит для бита, когда они передаются в качестве параметра или возвращаются из функции. Они также хранятся в стеке или, если они используются в качестве поля, внедрены в родительский объект, а не хранятся в отдельном месте в куче. Таким образом, структуры подходят для часто используемых данных, когда издержки доступа к куче являются проблемой. Дополнительные сведения о структурах см. в разделе структуры.

См. также