方法

方法是一个与类型关联的函数。 在面向对象的编程中,方法用于公开和实现对象和类型的功能和行为。

语法

// Instance method definition.
[ attributes ]
member [inline] self-identifier.method-name parameter-list [ : return-type ] =
    method-body

// Static method definition.
[ attributes ]
static member [inline] method-name parameter-list [ : return-type ] =
    method-body

// Abstract method declaration or virtual dispatch slot.
[ attributes ]
abstract member method-name : type-signature

// Virtual method declaration and default implementation.
[ attributes ]
abstract member method-name : type-signature
[ attributes ]
default self-identifier.method-name parameter-list [ : return-type ] =
    method-body

// Override of inherited virtual method.
[ attributes ]
override self-identifier.method-name parameter-list [ : return-type ] =
    method-body

// Optional and DefaultParameterValue attributes on input parameters
[ attributes ]
[ modifier ] member [inline] self-identifier.method-name ([<Optional; DefaultParameterValue( default-value )>] input) [ : return-type ]

备注

在以前的语法中,可以看到各种形式的方法声明和定义。 在较长的方法主体中,换行符跟在等号 (=) 之后,并且整个方法主体缩进。

属性可应用于任何方法声明。 它们在方法定义的语法之前,通常在单独的行上列出。 有关更多信息,请参阅特性

方法可以标记为 inline。 有关 inline 的信息,请参阅内联函数

非内联方法可以在类型中以递归方式使用;无需显式使用 rec 关键字。

实例方法

实例方法的声明方式如下:member 关键字和自标识符,后跟一个句点 (.) 以及方法名称和参数。 与 let 绑定的情况一样,参数列表可以是模式。 通常,以元组形式将方法参数括在括号中,这是在使用其他 .NET Framework 语言创建方法时在 F# 中出现的方式。 不过,柯里化形式(以空格分隔的参数)也很常见,并且还支持其他模式。

下面的示例说明了非抽象实例方法的定义和使用。

type SomeType(factor0: int) =
    let factor = factor0
    member this.SomeMethod(a, b, c) = (a + b + c) * factor

    member this.SomeOtherMethod(a, b, c) = this.SomeMethod(a, b, c) * factor

在实例方法中,请勿使用自标识符访问使用 let 绑定定义的字段。 访问其他成员和属性时,请使用自标识符。

静态方法

关键字 static 用于指定可以在没有实例的情况下调用方法,且该方法不与对象实例关联。 否则,方法是实例方法。

下一部分中的示例显示了使用 let 关键字声明的字段、使用 member 关键字声明的属性成员以及使用 static 关键字声明的静态方法。

下面的示例说明了静态方法的定义和使用。 假设这些方法定义位于上一部分中的 SomeType 类中。

static member SomeStaticMethod(a, b, c) =
   (a + b + c)

static member SomeOtherStaticMethod(a, b, c) =
   SomeType.SomeStaticMethod(a, b, c) * 100

抽象方法和虚方法

关键字 abstract 指示方法具有虚拟调度槽,并且可能在类中没有定义。 虚拟调度槽是内部维护的函数表中的一个条目,用于在运行时查找面向对象类型的虚拟函数调用。 虚拟调度机制是实现多形性的机制,多形性是面向对象编程的一个重要特征。 具有至少一个没有定义的抽象方法的类是抽象类,这意味着不能创建该类的任何实例。 有关抽象类的详细信息,请参阅抽象类

抽象方法声明不包括方法主体。 相反,该方法的名称后跟冒号 (:) 和该方法的类型签名。 方法的类型签名与在 Visual Studio 代码编辑器中将鼠标指针悬停在方法名称上时 IntelliSense 显示的类型签名相同,但没有参数名称。 当你以交互方式工作时,解释器 fsi.exe 也会显示类型签名。 方法的类型签名通过列出参数的类型,后跟返回类型和相应的分隔符来构成。 Curried 参数由 -> 分隔,元组参数由 * 分隔。 始终使用 -> 符号来将返回值与参数分隔。 括号可用于对复杂参数进行分组,例如当函数类型是参数时,或指示何时将元组视为单个参数而不是两个参数。

还可以通过将定义添加到类并使用 default 关键字来为抽象方法提供默认定义,如本主题中的语法块所示。 在同一类中定义的抽象方法等效于其他 .NET Framework 语言中的虚拟方法。 无论定义是否存在,abstract 关键字都会在该类的虚函数表中新建一个调度槽。

无论基类是否实现其抽象方法,派生类都可以提供抽象方法的实现。 若要在派生类中实现抽象方法,请在派生类中定义具有相同的名称和签名的方法,但使用 overridedefault 关键字,并提供方法主体。 关键字 overridedefault 的含义完全相同。 如果新方法重写基类实现,则使用 override;在与原始抽象声明相同的类中创建实现时,请使用 default。 不要针对实现基类中声明为抽象方法的方法使用 abstract 关键字。

下面的示例演示了具有默认实现的抽象方法 Rotate,该方法等效于 .NET Framework 虚拟方法。

type Ellipse(a0: float, b0: float, theta0: float) =
    let mutable axis1 = a0
    let mutable axis2 = b0
    let mutable rotAngle = theta0
    abstract member Rotate: float -> unit
    default this.Rotate(delta: float) = rotAngle <- rotAngle + delta

下面的示例演示了重写基类方法的派生类。 在此例中,重写会更改行为,从而使方法不执行任何操作。

type Circle(radius: float) =
    inherit Ellipse(radius, radius, 0.0)
    // Circles are invariant to rotation, so do nothing.
    override this.Rotate(_) = ()

重载方法

重载方法是在给定类型中具有相同名称但具有不同参数的方法。 在 F# 中,通常使用可选参数而不是重载方法。 但是,该语言支持使用重载方法,前提是参数是元组形式,而不是柯里化形式。

可选实参

从 F# 4.1 开始,还可以在方法中使用带有默认参数值的可选参数。 这是为了帮助促进与 C# 代码的互操作。 下面的示例演示了该语法:

open System.Runtime.InteropServices
// A class with a method M, which takes in an optional integer argument.
type C() =
    member _.M([<Optional; DefaultParameterValue(12)>] i) = i + 1

请注意,针对 DefaultParameterValue 传入的值必须与输入类型匹配。 在以上示例中,它是 int。 尝试将非整数值传递给 DefaultParameterValue 将导致编译错误。

示例:属性和方法

以下示例包含一个具有字段、专用函数、属性和静态方法示例的类型。

type RectangleXY(x1: float, y1: float, x2: float, y2: float) =
    // Field definitions.
    let height = y2 - y1
    let width = x2 - x1
    let area = height * width
    // Private functions.
    static let maxFloat (x: float) (y: float) = if x >= y then x else y
    static let minFloat (x: float) (y: float) = if x <= y then x else y
    // Properties.
    // Here, "this" is used as the self identifier,
    // but it can be any identifier.
    member this.X1 = x1
    member this.Y1 = y1
    member this.X2 = x2
    member this.Y2 = y2
    // A static method.
    static member intersection(rect1: RectangleXY, rect2: RectangleXY) =
        let x1 = maxFloat rect1.X1 rect2.X1
        let y1 = maxFloat rect1.Y1 rect2.Y1
        let x2 = minFloat rect1.X2 rect2.X2
        let y2 = minFloat rect1.Y2 rect2.Y2

        let result: RectangleXY option =
            if (x2 > x1 && y2 > y1) then
                Some(RectangleXY(x1, y1, x2, y2))
            else
                None

        result

// Test code.
let testIntersection =
    let r1 = RectangleXY(10.0, 10.0, 20.0, 20.0)
    let r2 = RectangleXY(15.0, 15.0, 25.0, 25.0)
    let r3: RectangleXY option = RectangleXY.intersection (r1, r2)

    match r3 with
    | Some(r3) -> printfn "Intersection rectangle: %f %f %f %f" r3.X1 r3.Y1 r3.X2 r3.Y2
    | None -> printfn "No intersection found."

testIntersection

另请参阅