方法
方法是一个与类型关联的函数。 在面向对象的编程中,方法用于公开和实现对象和类型的功能和行为。
语法
// 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
关键字都会在该类的虚函数表中新建一个调度槽。
无论基类是否实现其抽象方法,派生类都可以提供抽象方法的实现。 若要在派生类中实现抽象方法,请在派生类中定义具有相同的名称和签名的方法,但使用 override
或 default
关键字,并提供方法主体。 关键字 override
和 default
的含义完全相同。 如果新方法重写基类实现,则使用 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