方法 (F#)
「方法」(Method) 是與型別相關聯的函式。 在物件導向程式設計中,方法可用來公開及實作物件和型別的功能與行為。
// 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 也可以是模式。 您通常會將方法參數以 Tuple 形式括在括號中,這是以其他 .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 表示方法有虛擬分派位置,而且在類別中可能沒有定義。 「虛擬分派位置」(Virtual Dispatch Slot) 是內部維護函式資料表中的項目,於執行階段用來查閱物件導向型別中的虛擬函式呼叫。 虛擬分派機制會實作物件導向程式設計中的「多型」(Polymorphism) 這項重要功能。 至少有一個不含定義之抽象方法的類別稱為「抽象類別」(Abstract Class),這表示不可從該類別建立執行個體。 如需抽象類別的詳細資訊,請參閱抽象類別 (F#)。
抽象方法宣告不含方法主體, 而是後面接著冒號 (:) 以及方法之型別簽章的方法名稱。 方法的型別簽章與在 Visual Studio 程式碼編輯器中將滑鼠指標停放在方法名稱上方時 IntelliSense 所顯示的相同,但不含參數名稱。 型別簽章也可以透過互動使用解譯器 (fsi.exe) 來顯示。 方法的型別簽章是透過列出參數型別,後面接著傳回型別,並加上適當的分隔符號所形成。 局部調用參數是以 -> 分隔,而 Tuple 參數是以 * 分隔。 傳回值一定是以 -> 符號與引數隔開。 括號可用來分組複雜參數 (例如,當函式型別為參數時),或用來表示 Tuple 何時應視為單一參數而非兩個參數。
您也可以將定義加入至類別並使用 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# 中,通常會使用選擇性引數,而不使用多載方法。 不過,只要引數為 Tuple 形式,而非局部調用形式,此語言也允許多載方法。
範例:屬性和方法
在下列範例中,包含有欄位、私用函式、屬性和靜態方法範例的型別。
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