Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Записи представляют простые агрегаты именованных значений, при необходимости с элементами. Они могут быть структурами или ссылочными типами. По умолчанию они являются ссылочными типами.
Синтаксис
[ attributes ]
type [accessibility-modifier] typename =
[accessibility-modifier] {
[ mutable ] label1 : type1;
[ mutable ] label2 : type2;
...
}
[ member-list ]
Перед accessibility modifier тем, как typename влияет видимость всего типа и по public умолчанию.
accessibility modifier Второй влияет только на конструктор и поля.
Замечания
В предыдущем синтаксисе имя типа — это имя типа записи, метка1 и метка2 — это имена значений, называемые метками, а type1 и type2 — типы этих значений.
Список членов — это необязательный список элементов для типа. Атрибут можно использовать [<Struct>] для создания записи структуры, а не записи, которая является ссылочным типом.
Ниже приводятся некоторые примеры.
// Labels are separated by semicolons when defined on the same line.
type Point = { X: float; Y: float; Z: float }
// You can define labels on their own line with or without a semicolon.
type Customer =
{ First: string
Last: string
SSN: uint32
AccountNumber: uint32 }
// A struct record.
[<Struct>]
type StructPoint = { X: float; Y: float; Z: float }
Если каждая метка находится в отдельной строке, точка с запятой является необязательной.
Значения можно задать в выражениях, известных как выражения записей. Компилятор вводит тип из используемых меток (если метки достаточно отличаются от других типов записей). Фигурные скобки ({ }) заключены в выражение записи. В следующем коде показано выражение записи, которое инициализирует запись с тремя элементами с плавающей запятой с метками xy и z.
let mypoint = { X = 1.0; Y = 1.0; Z = -1.0 }
Не используйте сокращенную форму, если может быть другой тип, который также имеет те же метки.
type Point = { X: float; Y: float; Z: float }
type Point3D = { X: float; Y: float; Z: float }
// Ambiguity: Point or Point3D?
let mypoint3D = { X = 1.0; Y = 1.0; Z = 0.0 }
Метки последнего объявленного типа имеют приоритет над метками ранее объявленного типа, поэтому в предыдущем примере mypoint3D выводится значение Point3D. Вы можете явно указать тип записи, как показано в следующем коде.
let myPoint1 = { Point.X = 1.0; Y = 1.0; Z = 0.0 }
Методы можно определить для типов записей так же, как и для типов классов.
Создание записей с помощью выражений записей
Записи можно инициализировать с помощью меток, определенных в записи. Выражение, которое делает это, называется выражением записи. Используйте фигурные скобки, чтобы заключить выражение записи и использовать точку с запятой в качестве разделителя.
В следующем примере показано, как создать запись.
type MyRecord = { X: int; Y: int; Z: int }
let myRecord1 = { X = 1; Y = 2; Z = 3 }
Точка с запятой после последнего поля в выражении записи и определении типа необязательна независимо от того, находятся ли поля в одной строке.
При создании записи необходимо указать значения для каждого поля. Нельзя ссылаться на значения других полей в выражении инициализации для любого поля.
В следующем коде тип myRecord2 выводится из имен полей. При необходимости можно явно указать имя типа.
let myRecord2 =
{ MyRecord.X = 1
MyRecord.Y = 2
MyRecord.Z = 3 }
Другая форма построения записей может оказаться полезной при копировании существующей записи и, возможно, изменении некоторых значений полей. В следующей строке кода показано следующее.
let myRecord3 = { myRecord2 with Y = 100; Z = 2 }
Эта форма выражения записи называется выражением копирования и обновления записи.
Записи неизменяемы по умолчанию; однако можно легко создавать измененные записи с помощью выражения копирования и обновления. Можно также явно указать изменяемое поле.
type Car =
{ Make: string
Model: string
mutable Odometer: int }
let myCar =
{ Make = "Fabrikam"
Model = "Coupe"
Odometer = 108112 }
myCar.Odometer <- myCar.Odometer + 21
Не используйте атрибут DefaultValue с полями записи. Лучше всего определить экземпляры записей по умолчанию с полями, которые инициализированы по умолчанию, а затем использовать выражение копирования и обновления записи для задания полей, отличающихся от значений по умолчанию.
// Rather than use [<DefaultValue>], define a default record.
type MyRecord =
{ Field1 : int
Field2 : int }
let defaultRecord1 = { Field1 = 0; Field2 = 0 }
let defaultRecord2 = { Field1 = 1; Field2 = 25 }
// Use the with keyword to populate only a few chosen fields
// and leave the rest with default values.
let rr3 = { defaultRecord1 with Field2 = 42 }
Создание рекурсивных записей
Иногда при создании записи может потребоваться, чтобы она зависела от другого типа, который вы хотите определить после этого. Это ошибка компиляции, если вы не определите типы записей, которые будут взаимокурсивными.
Определение взаимно рекурсивных записей выполняется с помощью ключевого and слова. Это позволяет связать 2 или более типов записей вместе.
Например, следующий код определяет Person и Address тип как взаимно рекурсивный:
// Create a Person type and use the Address type that is not defined
type Person =
{ Name: string
Age: int
Address: Address }
// Define the Address type which is used in the Person record
and Address =
{ Line1: string
Line2: string
PostCode: string
Occupant: Person }
Чтобы создать экземпляры обоих, сделайте следующее:
// Create a Person type and use the Address type that is not defined
let rec person =
{
Name = "Person name"
Age = 12
Address =
{
Line1 = "line 1"
Line2 = "line 2"
PostCode = "abc123"
Occupant = person
}
}
Если бы вы определили предыдущий пример без ключевого and слова, то он не будет компилироваться. Ключевое and слово требуется для взаимно рекурсивных определений.
Сопоставление шаблонов с записями
Записи можно использовать с сопоставлением шаблонов. Можно явно указать некоторые поля и указать переменные для других полей, которые будут назначены при совпадении. В следующем примере кода показано следующее.
type Point3D = { X: float; Y: float; Z: float }
let evaluatePoint (point: Point3D) =
match point with
| { X = 0.0; Y = 0.0; Z = 0.0 } -> printfn "Point is at the origin."
| { X = xVal; Y = 0.0; Z = 0.0 } -> printfn "Point is on the x-axis. Value is %f." xVal
| { X = 0.0; Y = yVal; Z = 0.0 } -> printfn "Point is on the y-axis. Value is %f." yVal
| { X = 0.0; Y = 0.0; Z = zVal } -> printfn "Point is on the z-axis. Value is %f." zVal
| { X = xVal; Y = yVal; Z = zVal } -> printfn "Point is at (%f, %f, %f)." xVal yVal zVal
evaluatePoint { X = 0.0; Y = 0.0; Z = 0.0 }
evaluatePoint { X = 100.0; Y = 0.0; Z = 0.0 }
evaluatePoint { X = 10.0; Y = 0.0; Z = -1.0 }
Выходные данные этого кода приведены следующим образом.
Point is at the origin.
Point is on the x-axis. Value is 100.000000.
Point is at (10.000000, 0.000000, -1.000000).
Записи и члены
Вы можете указать элементы в записях, как и классы. Нет поддержки полей. Распространенный подход заключается в определении статического элемента для простого Default построения записей:
type Person =
{ Name: string
Age: int
Address: string }
static member Default =
{ Name = "Phillip"
Age = 12
Address = "123 happy fun street" }
let defaultPerson = Person.Default
Если вы используете идентификатор, этот идентификатор ссылается на экземпляр записи, член которой вызывается:
type Person =
{ Name: string
Age: int
Address: string }
member this.WeirdToString() =
this.Name + this.Address + string this.Age
let p = { Name = "a"; Age = 12; Address = "abc123" }
let weirdString = p.WeirdToString()
Модификаторы специальных возможностей в записях
В следующем коде показано использование модификаторов специальных возможностей. В проекте есть три файла: Module1.fsи Test1.fsTest2.fs. Внутренний тип записи и тип записи с частным конструктором определяются в module1.
// Module1.fs
module Module1
type internal internalRecd = { X: int }
type recdWithInternalCtor = private { Y: int }
Test1.fs В файле внутренняя запись должна быть инициализирована с модификатором internal доступа, так как уровень защиты значения и записи должен совпадать, и оба должны принадлежать одной сборке.
// Test1.fs
module Test1
open Module1
let myInternalRecd1 = { X = 2 } // This line will cause a compiler error.
let internal myInternalRecd2 = { X = 4 } // This is OK
Test2.fs В файле запись с частным конструктором не может быть инициализирована непосредственно из-за уровня защиты конструктора.
// Test2.fs
module Test2
open Module1
let myRecdWithInternalCtor = { Y = 6 } // This line will cause a compiler error.
Дополнительные сведения о модификаторах специальных возможностей см. в статье "Управление доступом ".
Различия между записями и классами
Поля записей отличаются от полей класса в том, что они автоматически предоставляются в качестве свойств, и они используются в создании и копировании записей. Запись строительства также отличается от строительства класса. В типе записи нельзя определить конструктор. Вместо этого применяется синтаксис конструкции, описанный в этом разделе. Классы не имеют прямой связи между параметрами конструктора, полями и свойствами.
Как и типы объединения и структуры, записи имеют семантику структурного равенства. Классы имеют семантику равенства ссылок. В следующем примере кода показано это.
type RecordTest = { X: int; Y: int }
let record1 = { X = 1; Y = 2 }
let record2 = { X = 1; Y = 2 }
if (record1 = record2) then
printfn "The records are equal."
else
printfn "The records are unequal."
Выходные данные этого кода приведены следующим образом:
The records are equal.
При написании одного кода с классами два объекта класса будут неравными, так как два значения будут представлять два объекта в куче, и будут сравниваться только адреса (если только тип класса не переопределяет System.Object.Equals метод).
Если требуется равенство ссылок для записей, добавьте атрибут [<ReferenceEquality>] над записью.