Поделиться через


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

Дополнительные сведения см. в разделе Конструкторы (F#).

Привязки 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#).

См. также

Ссылки

Наследование (F#)

Интерфейсы (F#)

Основные понятия

Члены (F#)

Другие ресурсы

Справочник по языку F#