類別 (F#)
「類別」(Class) 是表示可以有屬性、方法和事件之物件的型別。
// 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 描述選擇性泛型型別參數。 它是由角括號 (< 和 >) 括住的型別參數名稱和條件約束所組成。 如需詳細資訊,請參閱 泛型 (F#)和條件約束 (F#)。 parameter-list 描述建構函式參數。 第一個存取修飾詞與型別有關;第二個與主要建構函式有關。 在這兩種情況下,預設值是public.
您可以使用關鍵字 inherit 來指定類別的基底類別。 您必須將括號中的引數提供給基底類別建構函式。
您可以使用 let 繫結來宣告類別的區域欄位或函數值,而且必須遵循 let 繫結的一般規則。 do-bindings 區段包含物件在建構時要執行的程式碼。
member-list 是由其他建構函式、執行個體和靜態方法宣告、介面宣告、抽象繫結,以及屬性和事件宣告所組成。 這些內容將在成員 (F#) 加以說明。
搭配選擇性 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)
如需詳細資訊,請參閱建構函式 (F#)。
let 和 do 繫結
類別定義中的 let 和 do 繫結形成主要類別建構函式主體,因此會在類別執行個體建立時執行。 如果 let 繫結是函式,則會編譯為成員。 如果 let 繫結是未在任何函式或成員中使用的值,則會編譯為建構函式的區域變數, 否則會編譯為類別的欄位。 後面的 do 運算式會編譯為主要建構函式,並執行每個執行個體的初始設定程式碼。 因為任何其他建構函式一定會呼叫主要建構函式,所以無論呼叫哪個建構函式,let 繫結和 do 繫結一定都會執行。
let 繫結所建立的欄位都可由類別的方法和屬性存取,但不能從靜態方法存取,即使靜態方法接受執行個體變數做為參數也一樣。 這些欄位不能透過自我識別項 (如果有的話) 來存取。
自我識別項
「自我識別項」(Self Identifier) 是表示目前執行個體的名稱。 自我識別項類似 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 關鍵字宣告的自我識別項要等到 let 繫結執行之後才會初始化。 因此,它不能用於 let 繫結中。 您可以在 do 繫結區段中使用此自我識別項。
泛型型別參數
泛型型別參數是在角括號 (< 和 >) 中指定,形式為一個單引號,後面接著識別項。 多個泛型型別參數之間以逗號來分隔。 泛型型別參數是在整個宣告的範圍內。 在下列程式碼範例中,會示範如何指定泛型型別參數。
type MyGenericClass<'a> (x: 'a) =
do printfn "%A" x
型別引數是在使用型別時推斷的。 在下列程式碼中,推斷的型別是 Tuple 序列。
let g1 = MyGenericClass( seq { for i in 1 .. 10 -> (i, i*i) } )
指定繼承
inherit 子句會識別直接基底類別 (如果有的話)。 F# 只允許使用一個直接基底類別。 類別所實作的介面不視為基底類別。 介面 (F#) 主題將討論介面。
您可以使用語言關鍵字 base 做為識別項,後面接著句號 (.) 和成員名稱,從衍生類別存取基底類別的方法和屬性。
如需詳細資訊,請參閱繼承 (F#)。
成員區段
您可以在此區段中定義靜態或執行個體方法、屬性、介面實作、抽象成員、事件宣告和其他建構函式。 let 和 do 繫結不可以出現在此區段中。 因為除了類別之外,成員還可以加入各種 F# 型別中,所以將在另一個主題成員 (F#) 中討論成員。
相互遞迴型別
當您定義以循環方式彼此參考的型別時,可以使用 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 程式庫) 擴充時,就很適合使用類別。
如果不打算與物件導向程式碼密切相互操作,或者如果要撰寫獨立使用、不會與物件導向程式碼頻繁互動的程式碼,您應該考慮使用記錄和已區分的聯集。 一般而言,使用單一考慮周密的已區分聯集加上適當的模式比對程式碼,即可做為物件階層架構的簡易替代方案。 如需已區分之聯集的詳細資訊,請參閱已區分的聯集 (F#)。
記錄的優點是比類別更簡易,但是在型別的需求超出其簡易性所能完成時,便不適合使用記錄。 記錄基本上是簡易的值彙總,沒有可執行自訂動作的個別建構函式、沒有隱藏的欄位,也沒有繼承或介面實作。 雖然屬性和方法等成員可以加入記錄中,讓記錄行為更為複雜,但是記錄中儲存的欄位仍然是簡易的值彙總。 如需記錄的詳細資訊,請參閱資料錄 (F#)。
結構也適用於小型資料彙總,但不同於類別和記錄,結構是 .NET 實值型別, 類別和記錄則是 .NET 參考型別。 實值型別和參考型別語意的差異在於實值型別是以傳值方式傳遞。 這表示它們在當做參數傳遞或從函式傳回時,是以位元對位元的方式複製。 它們也會儲存在堆疊上,如果做為欄位則會內嵌在父物件中,而不是儲存在堆積上各自不同的位置。 因此,考慮到存取堆積會造成額外負荷,結構就很適合用於經常存取的資料。 如需結構的詳細資訊,請參閱結構 (F#)。