方法

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

语法

// 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 关键字在类的虚拟函数表中创建一个新的调度槽。

无论基类是否实现其抽象方法,派生类都可以提供抽象方法的实现。 若要在派生类中实现抽象方法,请定义在派生类中具有相同名称和签名的方法,但使用 overridedefault 关键字除外,并提供方法正文。 关键字 overridedefault 含义完全相同。 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

另请参阅