方法 (F#)
方法是与类型关联的函数。在面向对象的编程中,方法用于公开和实现对象及类型的功能和行为。
// 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 self-identifier.method-name : type-signature
// Virtual method declaration and default implementation.
[ attributes ]
abstract member [inline] self-identifier.method-name : type-signature
[ attributes ]
default member [inline] self-identifier.method-name parameter-list[ : return-type ] =
method-body
// Override of inherited virtual method.
[ attributes ]
override member [inline] self-identifier.method-name parameter-list [ : return-type ]=
method-body
备注
在前面的语法中,您可以看到各种形式的方法声明和定义。在较长的方法体中,等号 (=) 后跟换行符,并且整个方法体将缩进。
可以将特性应用于任何方法声明。这些特性位于方法定义语法的前面,并且通常列在单独的行中。有关更多信息,请参见特性 (F#)。
可以将方法标记为 inline。有关 inline 的信息,请参见内联函数 (F#)。
可以按递归方式在该类型中使用非内联的方法;无需显式使用 rec 关键字。
实例方法
实例方法是用 member 关键字和 self-identifier 声明的,后跟一个句点 (.) 和方法名称与参数。与 let 绑定的情况一样,parameter-list 可以为一个模式。通常,您以元组形式将方法参数括在括号中,如果方法是用其他 .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 指示方法具有虚拟调度槽,并且可能在类中没有定义。虚拟调度槽是内部维护的函数表中的一个条目,该条目用于在运行时查找面向对象的类型中的虚函数调用。虚拟调度机制是一种实现“多态性”的机制,多态性是面向对象的编程中的一项重要功能。至少具有一个没有定义的抽象方法的类是“抽象类”,这意味着无法创建该类的任何实例。有关抽象类的更多信息,请参见抽象类 (F#)。
抽象方法声明不包括方法体,而是方法的名称后跟一个冒号 (:) 和方法的类型签名。方法的类型签名与在 Visual Studio 代码编辑器中将鼠标指针悬停在方法名称上时 IntelliSense 显示的类型签名相同,只是没有参数名称。当以交互方式工作时,解释器 fsi.exe 也会显示类型签名。方法的类型签名是通过列出参数的类型(后跟返回类型以及相应的分隔符)而形成的。扩充参数由 -> 分隔,元组参数由 * 分隔。返回值始终由 -> 符号与参数隔开。可以使用括号将复杂的参数分组(例如当函数类型为参数时),或者可以使用括号来指示何时将元组视为单一参数(而不是两个参数)。
您也可以通过向类中添加定义并使用 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
下面的示例阐释一个重写基类方法的派生类。在本例中,override 将更改行为,因此方法不执行任何操作。
type Circle(radius : float) =
inherit Ellipse(radius, radius, 0.0)
// Circles are invariant to rotation, so do nothing.
override this.Rotate(_) = ()
重载方法
重载方法是在给定类型中具有相同名称但参数不同的方法。在 F# 中,通常会使用可选参数,而不是重载方法。不过,允许在该语言中使用重载方法,只要参数采用元组形式(而不是扩充形式)即可。
示例:属性和方法
下面的示例包含一个类型,该类型具有字段、私有函数、属性和静态方法的示例。
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