属性 (F#)

属性是表示与对象关联的值的成员。

语法

// Property that has both get and set defined.
[ attributes ]
[ static ] member [accessibility-modifier] [self-identifier.]PropertyName
with [accessibility-modifier] get() =
    get-function-body
and [accessibility-modifier] set parameter =
    set-function-body

// Alternative syntax for a property that has get and set.
[ attributes-for-get ]
[ static ] member [accessibility-modifier-for-get] [self-identifier.]PropertyName =
    get-function-body
[ attributes-for-set ]
[ static ] member [accessibility-modifier-for-set] [self-identifier.]PropertyName
with set parameter =
    set-function-body

// Property that has get only.
[ attributes ]
[ static ] member [accessibility-modifier] [self-identifier.]PropertyName =
    get-function-body

// Alternative syntax for property that has get only.
[ attributes ]
[ static ] member [accessibility-modifier] [self-identifier.]PropertyName
with get() =
    get-function-body

// Property that has set only.
[ attributes ]
[ static ] member [accessibility-modifier] [self-identifier.]PropertyName
with set parameter =
    set-function-body

// Automatically implemented properties.
[ attributes ]
[ static ] member val [accessibility-modifier] PropertyName = initialization-expression [ with get, set ]

备注

属性在面向对象的编程中表示“具有”关系,表示与对象实例关联的数据,或与类型关联的数据(针对静态属性)。

可通过两种方式声明属性,具体取决于是否要为属性显式指定基础值(也称为后备存储),或者是否允许编译器自动生成后备存储。 通常,如果属性具有“非普通”实现,则应该使用更显式的方式;如果属性只是值或变量的简单包装器,则应该使用自动方式。 若要显式声明属性,请使用 member 关键字。 此声明性语法后面是指定 getset 方法(也称为访问器)的语法。 语法部分中显示的显式语法的各种形式可用于读/写、只读和只写属性。 对于只读属性,只定义一个 get 方法;对于只写属性,只定义一个 set 方法。 请注意,当属性具有 getset 访问器时,可使用替代语法来为每个访问器指定不同的属性和辅助功能修饰符,如以下代码所示。

// A read-only property.
member this.MyReadOnlyProperty = myInternalValue
// A write-only property.
member this.MyWriteOnlyProperty with set (value) = myInternalValue <- value
// A read-write property.
member this.MyReadWriteProperty
    with get () = myInternalValue
    and set (value) = myInternalValue <- value

对于具有 getset 方法的读/写属性,可以反转 getset 的顺序。 或者,可以提供仅为 get 显示的语法和仅为 set 显示的语法,而不是使用组合语法。 这样做可以更轻松地禁止注释单个 getset 方法(如果你可能需要执行此操作)。 以下代码演示了使用组合语法的替代方法。

member this.MyReadWriteProperty with get () = myInternalValue
member this.MyReadWriteProperty with set (value) = myInternalValue <- value

保存属性的数据的专用值称为“后备存储”。 若要让编译器自动创建后备存储,请使用关键字 member val,省略自我标识符,然后提供一个表达式来初始化属性。 如果该属性是可变的,则包括 with get, set。 例如,下面的类类型包括两个自动实现的属性。 Property1 为只读,它被初始化为提供给主构造函数的参数,而 Property2 是一个初始化为空字符串的可设置属性:

type MyClass(property1 : int) =
member val Property1 = property1
member val Property2 = "" with get, set

自动实现的属性是类型初始化的一部分,因此必须在任何其他成员定义之前包含它们,就像类型定义中的 let 绑定和 do 绑定一样。 请注意,初始化自动实现的属性的表达式仅在初始化时求值,而不是在每次访问该属性时求值。 此行为与显式实现的属性的行为相反。 这实际上意味着用于初始化这些属性的代码将被添加到类的构造函数中。 请考虑显示这种差异的以下代码:

type MyClass() =
    let random  = new System.Random()
    member val AutoProperty = random.Next() with get, set
    member this.ExplicitProperty = random.Next()

let class1 = new MyClass()

printfn $"class1.AutoProperty = %d{class1.AutoProperty}"
printfn $"class1.ExplicitProperty = %d{class1.ExplicitProperty}"

输出

class1.AutoProperty = 1853799794
class1.AutoProperty = 1853799794
class1.ExplicitProperty = 978922705
class1.ExplicitProperty = 1131210765

上述代码的输出显示,AutoProperty 的值在被重复调用时不会发生更改,而 ExplicitProperty 则会在每次调用时发生更改。 这说明自动实现的属性的表达式不是每次都会求值,就和显式属性的 getter 方法一样。

警告

某些库(例如实体框架 (System.Data.Entity))在基类构造函数中执行自定义操作,在初始化自动实现的属性时这些操作无法正常工作。 在这些情况下,请尝试使用显式属性。

属性可以是类、结构、可区分联合、记录、接口和类型扩展的成员,也可在对象表达式中定义。

特性可应用于属性。 若要将特性应用于属性,请将其单独写在该属性之前的行上。 有关更多信息,请参阅特性

默认情况下,属性是公开的。 辅助功能修饰符也可以应用于属性。 若要应用辅助功能修饰符,请直接在属性名称之前添加它(如果要将其应用于 getset 方法);如果每个访问器需要不同的辅助功能,请将其添加到 getset 关键字之前。 辅助功能修饰符可以是 publicprivateinternal 中的其中一个。 有关详细信息,请参阅访问控制

每次访问属性时,都将执行属性实现。

静态属性和实例属性

属性可以是静态属性或实例属性。 可在没有实例的情况下调用静态属性,并将其用于与类型(而不是单个对象)关联的值。 对于静态属性,请省略自我标识符。 自我标识符对于实例属性是必需的。

下面的静态属性定义是基于这样一个场景,即你有一个作为属性的后备存储的静态字段 myStaticValue

static member MyStaticProperty
    with get() = myStaticValue
    and set(value) = myStaticValue <- value

属性也可以是类似数组的,在这种情况下,它们被称为索引属性。 有关详细信息,请参阅索引属性

属性的类型批注

在许多情况下,根据编译器提供的信息就足以从后备存储的类型推断出属性的类型,但你可以通过添加类型批注来显式设置类型。

// To apply a type annotation to a property that does not have an explicit
// get or set, apply the type annotation directly to the property.
member this.MyProperty1 : int = myInternalValue
// If there is a get or set, apply the type annotation to the get or set method.
member this.MyProperty2 with get() : int = myInternalValue

使用属性 set 访问器

可使用 set 运算符设置提供 <- 访问器的属性。

// Assume that the constructor argument sets the initial value of the
// internal backing store.
let mutable myObject = new MyType(10)
myObject.MyProperty <- 20
printfn "%d" (myObject.MyProperty)

输出为 20。

抽象属性

属性可以是抽象的。 与方法一样,abstract 仅表示存在与属性关联的虚拟调度。 抽象属性可以是真正抽象的,即同一类中没有定义。 因而,包含此类属性的类是抽象类。 另外,抽象也可以仅表示属性是虚拟的,在这种情况下,同一类中必须存在定义。 注意,抽象属性不能是专用的,如果一个访问器是抽象的,另一个访问器也必须是抽象的。 有关抽象类的详细信息,请参阅抽象类

// Abstract property in abstract class.
// The property is an int type that has a get and
// set method
[<AbstractClass>]
type AbstractBase() =
    abstract Property1: int with get, set

// Implementation of the abstract property
type Derived1() =
    inherit AbstractBase()
    let mutable value = 10

    override this.Property1
        with get () = value
        and set (v: int) = value <- v

// A type with a "virtual" property.
type Base1() =
    let mutable value = 10
    abstract Property1: int with get, set

    default this.Property1
        with get () = value
        and set (v: int) = value <- v

// A derived type that overrides the virtual property
type Derived2() =
    inherit Base1()
    let mutable value2 = 11

    override this.Property1
        with get () = value2
        and set (v) = value2 <- v

另请参阅