類別 (F#)
類別是表示可以有屬性、方法和事件之物件的型別。
語法
// Class definition:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
[ class ]
[ inherit base-type-name(base-constructor-args) ]
[ let-bindings ]
[ do-bindings ]
member-list
...
[ end ]
// Mutually recursive class definitions:
type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...
備註
類別代表 .NET 物件類型的基本描述;類別是支援 F# 中物件導向程式設計的主要型別概念。
在上述語法中,type-name
是任何有效的識別碼。 type-params
描述選擇性的泛型型別參數。 包含以方括號 (<
和 >
) 括住的型別參數名稱和條件約束。 如需詳細資訊,請參閱泛型和條件約束。 parameter-list
描述建構函式參數。 第一個存取修飾詞與型別相關;第二個與主要建構函式相關。 在這兩種情況下,預設值為 public
。
您可以使用 inherit
關鍵字來指定類別的基底類別。 您必須在括號中提供基底類別建構函式的引數。
您可以使用 let
繫結宣告類別本機的欄位或函式值,而且您必須遵循 let
繫結的一般規則。 do-bindings
區段包含要在物件建構時執行的程式碼。
member-list
包含其他建構函式、執行個體和靜態方法宣告、介面宣告、抽象繫結,以及屬性和事件宣告。 這些描述於 Members 中。
與選擇性 as
關鍵字搭配使用的 identifier
,會提供執行個體變數的名稱或自我識別碼,可用於型別定義中,以參考型別的執行個體。 如需詳細資訊,請參閱這個主題稍後的「自我識別碼」一節。
標記定義開頭和結尾的關鍵字 class
和 end
是選擇性的。
相互遞迴型別是彼此參考的型別,會與 and
關鍵字聯結在一起,就像相互遞迴函式一樣。 如需範例,請參閱「相互遞迴型別」一節。
建構函式
建構函式是建立類別型別執行個體的程式碼。 類別的建構函式在 F# 中的運作方式與其他 .NET 語言不同。 在 F# 類別中,一律有一個主要建構函式,其引數會在後面接續型別名稱的 parameter-list
中描述,其主體是由類別宣告開頭的 let
(和 let rec
) 繫結與後續的 do
繫結所組成。 主要建構函式的引數位於整個類別宣告的範圍內。
您可以使用 new
關鍵字新增成員來新增其他建構函式,如下所示:
new
(argument-list
) = constructor-body
新建構函式的主體必須叫用在類別宣告頂端所指定的主要建構函式。
下列範例說明此概念。 在下列程式碼中,MyClass
有兩個建構函式:採用兩個引數的主要建構函式,以及不採用任何引數的另一個建構函式。
type MyClass1(x: int, y: int) =
do printfn "%d %d" x y
new() = MyClass1(0, 0)
let 和 do 繫結
類別定義中的 let
和 do
繫結會形成主要類別建構函式的主體,因此每當建立類別執行個體時,就會執行這些繫結。 如果 let
繫結是函式,則會編譯為成員。 如果 let
繫結是未用於任何函式或成員的值,則會編譯為建構函式本機的變數。 否則,會編譯為類別的欄位。 後續的 do
運算式會編譯為主要建構函式,並針對每個執行個體執行初始化程式碼。 由於任何其他建構函式一律會呼叫主要建構函式,因此不論呼叫哪一個建構函式,let
繫結和 do
繫結一律都會執行。
let
繫結所建立的欄位可以在整個類別的方法和屬性中存取;不過,即使靜態方法採用執行個體變數作為參數,也無法從靜態方法存取欄位。 如果存在,則無法使用自我識別碼來存取。
自我識別碼
自我識別碼是代表目前執行個體的名稱。 自我識別碼類似於 C# 或 C++ 中的 this
關鍵字,或 Visual Basic 中的 Me
。 您可以透過兩種不同的方式定義自我識別碼,取決於您想要讓自我識別碼在整個類別定義的範圍,或只針對個別方法的範圍。
若要定義整個類別的自我識別碼,請在建構函式參數清單的結尾括號後面使用 as
關鍵字,並指定識別碼名稱。
若要僅針對一個方法定義自我識別碼,請在成員宣告中提供自我識別碼,就在方法名稱前面,使用句號 (.) 作為分隔符號。
下列程式碼範例說明建立自我識別碼的兩種方式。 在第一行中,as
關鍵字是用來定義自我識別碼。 在第五行中,識別碼 this
是用來定義範圍限制為 PrintMessage
方法的自我識別碼。
type MyClass2(dataIn) as self =
let data = dataIn
do
self.PrintMessage()
member this.PrintMessage() =
printf "Creating MyClass2 with Data %d" data
不同於其他 .NET 語言,您可以視需要命名自我識別碼;不限於 self
、Me
或 this
等名稱。
在基底建構函式之後,不會初始化以 as
關鍵字宣告的自我識別碼。 因此,在基底建構函式之前或內部使用時,System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized.
會在執行階段期間引發。 您可以在基底建構函式之後自由使用自我識別碼,例如 let
繫結或 do
繫結中。
泛型類型參數
泛型型別參數是以角括號 (<
和 >
) 指定,格式為單引號,後面接著識別碼。 以逗號分隔多個泛型型別參數。 泛型型別參數在整個宣告範圍內。 下列程式碼範例示範如何指定泛型型別參數。
type MyGenericClass<'a>(x: 'a) =
do printfn "%A" x
使用型別時,會推斷型別引數。 在下列程式碼中,推斷的型別是元組序列。
let g1 = MyGenericClass(seq { for i in 1..10 -> (i, i * i) })
指定繼承
inherit
子句會識別直接基底類別,如果有的話。 在 F# 中,只允許一個直接基底類別。 類別實作的介面不被視為基底類別。 介面會在 Interfaces 主題中討論。
您可以使用語言關鍵字 base
作為識別碼,後面接著句號 (.) 和成員的名稱,從衍生類別存取基底類別的方法和屬性。
如需詳細資訊,請參閱繼承。
Members 區段
您可以在本區段中定義靜態或執行個體方法、屬性、介面實作、抽象成員、事件宣告和其他建構函式。 Let 和 do 繫結無法顯示在本區段中。 由於成員除了類別之外,還可以新增至各種 F# 型別,因此會在個別的主題 Members 中討論。
相互遞迴型別
當您以循環方式定義參考彼此的型別時,您會使用 and
關鍵字將型別定義字串在一起。 and
關鍵字會取代第一個定義以外的所有 type
關鍵字,如下所示。
open System.IO
type Folder(pathIn: string) =
let path = pathIn
let filenameArray: string array = Directory.GetFiles(path)
member this.FileArray = Array.map (fun elem -> new File(elem, this)) filenameArray
and File(filename: string, containingFolder: Folder) =
member this.Name = filename
member this.ContainingFolder = containingFolder
let folder1 = new Folder(".")
for file in folder1.FileArray do
printfn "%s" file.Name
輸出是目前目錄中所有檔案的清單。
使用類別、聯集、記錄和結構的時機
根據可供選擇的各種型別,您必須充分了解每個型別的設計,以針對特定情況選取適當的型別。 類別是專為在物件導向程式設計內容中使用而設計。 物件導向程式設計是在針對 .NET Framework 撰寫之應用程式中使用的主控架構。 如果您的 F# 程式碼必須與 .NET Framework 或其他物件導向程式庫緊密搭配使用,特別是您必須從物件導向型別系統擴充,例如 UI 程式庫,則類別可能很適合。
如果您不是與物件導向程式碼緊密互通,或者您撰寫的程式碼是獨立式,因此會受到保護,免於與物件導向程式碼的頻繁互動,您應該考慮使用混合的類別、記錄和已區分的聯集。 單一、妥善考慮的已區分的聯集,以及適當的模式比對程式碼,通常可用來作為物件階層更簡單的替代方案。 如需已區分的聯集的詳細資訊,請參閱已區分的聯集。
記錄的優點是比類別更簡單,但是當型別的需求超過可使用其簡單性來完成的作業時,記錄就不適用。 記錄基本上是簡單的值彙總,不需要個別的建構函式,這些建構函式可以執行自訂動作、不含隱藏欄位,以及沒有繼承或介面實作。 雖然屬性和方法等成員可以新增至記錄,使其行為更為複雜,但儲存在記錄中的欄位仍是簡單的值彙總。 如需有關記錄的詳細資訊,請參閱記錄。
結構也適用於小型資料彙總,但是結構與類別和記錄不同,因為結構是 .NET 實值型別。 類別和記錄是 .NET 參考型別。 實值型別和參考型別的語意不同,實值型別會以值傳遞。 這表示當其以參數的形式傳遞或從函式傳回時,會以少量的方式複製。 也會儲存在堆疊上,或是如果當做欄位使用,會內嵌在父物件內,而不是儲存在堆積上自己的個別位置。 因此,當存取堆積的額外負荷是一個問題時,結構就適合經常存取的資料。 如需結構的詳細資訊,請參閱結構。