显式字段:val 关键字
val
关键字用于声明在类或结构类型中存储一个值(但不初始化该值)的位置。 以这种方式声明的存储位置被称为“显式字段”。 关键字 val
的另一个用法与 member
关键字结合使用来声明自动实现的属性。 有关自动实现的属性的详细信息,请参阅 “属性”。
语法
val [ mutable ] [ access-modifier ] field-name : type-name
备注
在类或结构类型中定义字段的通常方式是使用 let
绑定。 但是,let
绑定必须初始化为类构造函数的一部分,而这并不总是可能、有必要或必需实现的。 当你需要一个未初始化的字段时,可以使用 val
关键。
显式字段可以为静态或非静态的。 access-modifier 可以是 public
、private
或 internal
。 默认情况下,显式字段是公共的。 这不同于类中的 let
绑定,后者始终是私有的。
具有主构造函数的类类型中的显式字段上需要 DefaultValue 特性。 此特性指定该字段被初始化为零。 字段的类型必须支持零初始化。 如果一个类型为以下类型之一,则该类型支持零初始化:
- 具有零值的基元类型。
- 一种支持 Null 值作为标准值、异常值或值表示形式的类型。 这包括类、元组、记录、函数、接口、.NET 引用类型、
unit
类型以及可区分联合类型。 - 一个 .NET 值类型。
- 一种结构,其字段均支持默认值零。
例如,称为 someField
的不可变字段具有一个 .NET 编译表示形式的支持字段,该字段名为 someField@
,你可以使用名为 someField
的属性访问存储值。
对于可变字段,.NET 编译的表示形式是一个 .NET 字段。
警告
.NET Framework 命名空间 System.ComponentModel
包含具有相同名称的特性。 有关该属性的信息,请参见 DefaultValueAttribute。
以下代码展示了显式字段的用法,作为对比,还展示了具有主构造函数的类中的 let
绑定。 注意:与 let
字段绑定的 myInt1
是私有的。 当从成员方法引用与 let
字段绑定的 myInt1
时,不需要自我标识符 this
。 但如果要引用显式字段 myInt2
和 myString
,则需要自我标识符。
type MyType() =
let mutable myInt1 = 10
[<DefaultValue>] val mutable myInt2 : int
[<DefaultValue>] val mutable myString : string
member this.SetValsAndPrint( i: int, str: string) =
myInt1 <- i
this.myInt2 <- i + 1
this.myString <- str
printfn "%d %d %s" myInt1 (this.myInt2) (this.myString)
let myObject = new MyType()
myObject.SetValsAndPrint(11, "abc")
// The following line is not allowed because let bindings are private.
// myObject.myInt1 <- 20
myObject.myInt2 <- 30
myObject.myString <- "def"
printfn "%d %s" (myObject.myInt2) (myObject.myString)
输出如下所示:
11 12 abc
30 def
以下代码展示了不具有主构造函数的类中的显式字段的用法。 在该例中,不需要 DefaultValue
特性,但所有字段必须在为该类型定义的构造函数中进行初始化。
type MyClass =
val a : int
val b : int
// The following version of the constructor is an error
// because b is not initialized.
// new (a0, b0) = { a = a0; }
// The following version is acceptable because all fields are initialized.
new(a0, b0) = { a = a0; b = b0; }
let myClassObj = new MyClass(35, 22)
printfn "%d %d" (myClassObj.a) (myClassObj.b)
输出为 35 22
。
以下代码展示了结构中显式字段的用法。 由于结构是值类型,它自动具有无参数构造函数,该函数将其字段的值设置为零。 因此,不需要 DefaultValue
特性。
type MyStruct =
struct
val mutable myInt : int
val mutable myString : string
end
let mutable myStructObj = new MyStruct()
myStructObj.myInt <- 11
myStructObj.myString <- "xyz"
printfn "%d %s" (myStructObj.myInt) (myStructObj.myString)
输出为 11 xyz
。
请注意,如果要使用不带 mutable
关键字的 mutable
字段初始化结构,则分配将作用于结构的副本,该副本将在分配后立即丢弃。 因此,结构不会更改。
[<Struct>]
type Foo =
val mutable bar: string
member self.ChangeBar bar = self.bar <- bar
new (bar) = {bar = bar}
let foo = Foo "1"
foo.ChangeBar "2" //make implicit copy of Foo, changes the copy, discards the copy, foo remains unchanged
printfn "%s" foo.bar //prints 1
let mutable foo' = Foo "1"
foo'.ChangeBar "2" //changes foo'
printfn "%s" foo'.bar //prints 2
显式字段不适用于例程使用。 通常,在可能的情况下,应在类中使用 let
绑定,而不是显式字段。 显式字段在某些互操作性方案中十分有用,例如,当需要定义一个结构,该结构将用在对本机 API 的平台调用中,或用在 COM 互操作方案中。 有关详细信息,请参阅外部函数。 另一种要用到显式字段的情况是当使用 F# 代码生成器时,该生成器会发出不具有主构造函数的类。 显式字段对于线程静态变量或类似的构造而言也十分有用。 有关详细信息,请参阅 System.ThreadStaticAttribute
。
当关键字 member val
在一个类型定义中同时出现,它就是一个自动实现属性的定义。 有关详细信息,请参阅 属性。