方法是与类型关联的函数。 在面向对象的编程中,方法用于公开和实现对象和类型的功能和行为。
语法
// 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
指示方法具有虚拟调度槽,并且该类中可能没有定义。
虚拟调度槽是内部维护的函数表中的条目,用于在运行时查找面向对象的类型的虚拟函数调用。 虚拟调度机制是实现 多态性的机制,这是面向对象的编程的重要特征。 至少有一个没有定义的抽象方法的 类是一个抽象类,这意味着无法创建该类的实例。 有关抽象类的详细信息,请参阅 抽象类。
抽象方法声明不包括方法主体。 相反,该方法的名称后跟冒号(:)和方法的类型签名)。 方法的类型签名与 IntelliSense 在 Visual Studio Code 编辑器中将鼠标指针悬停在方法名称上时所显示的方法签名相同,但不包含参数名称。 当你以交互方式工作时,解释器 fsi.exe也会显示类型签名。 方法的类型签名通过列出参数的类型(后跟返回类型)和相应的分隔符组成。 Curried 参数由 ->
两者分隔,元组参数由两者 *
分隔。 返回值始终由符号 ->
与参数分隔。 括号可用于对复杂参数进行分组,例如函数类型为参数时,或指示元组何时被视为单个参数而不是两个参数。
还可以通过向类添加定义并使用 default
关键字来提供抽象方法默认定义,如本主题中的语法块所示。 在同一类中具有定义的抽象方法等效于其他 .NET Framework 语言中的虚拟方法。 无论是否存在定义, abstract
关键字在类的虚拟函数表中创建一个新的调度槽。
无论基类是否实现其抽象方法,派生类都可以提供抽象方法的实现。 若要在派生类中实现抽象方法,请定义在派生类中具有相同名称和签名的方法,但使用 override
或 default
关键字除外,并提供方法正文。 关键字 override
和 default
含义完全相同。
override
如果新方法重写基类实现,请在与原始抽象声明相同的类中创建实现时使用default
。 不要对 abstract
实现在基类中声明为抽象的方法的方法使用关键字。
下面的示例演示了具有默认实现(等效于 .NET Framework 虚拟方法)的抽象方法 Rotate
。
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# 中,通常使用可选参数,而不是重载的方法。 但是,如果参数采用元组形式而不是 curried 形式,则允许使用语言重载的方法。 以下示例演示了它:
type MyType(dataIn: int) =
let data = dataIn
member this.DoSomething(a: int) = a + data
member this.DoSomething(a: string) = sprintf "Hello world, %s!" a
let m = MyType(10)
printfn "With int: %d" (m.DoSomething(2)) // With int: 12
printfn "With string: %s" (m.DoSomething("Bill")) // With string: Hello world, Bill!
可选参数
从 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