介面 (F#)
介面會指定其他類別應提供實作的一組相關成員。
語法
// Interface declaration:
[ attributes ]
type [accessibility-modifier] interface-name =
[ interface ] [ inherit base-interface-name ...]
abstract member1 : [ argument-types1 -> ] return-type1
abstract member2 : [ argument-types2 -> ] return-type2
...
[ end ]
// Implementing, inside a class type definition:
interface interface-name with
member self-identifier.member1argument-list = method-body1
member self-identifier.member2argument-list = method-body2
// Implementing, by using an object expression:
[ attributes ]
let class-name (argument-list) =
{ new interface-name with
member self-identifier.member1argument-list = method-body1
member self-identifier.member2argument-list = method-body2
[ base-interface-definitions ]
}
member-list
備註
介面宣告類似於類別宣告,但未實作任何成員。 相反地,所有成員都是抽象的,如關鍵字 abstract
所表示。 您並未提供抽象方法的方法主體。 F# 無法在介面上定義預設方法實作,但與 C# 所定義的預設實作相容。 只有在繼承自非介面基底類別時,才支援使用 default
關鍵字的預設實作。
介面的預設協助工具是 public
。
您可以選擇性地使用一般 F# 語法為每個方法參數提供名稱:
type ISprintable =
abstract member Print: format: string -> unit
在上述 ISprintable
範例中,Print
方法具有名稱為 format
的類型 string
單一參數。
有兩種方式可以實作介面:使用物件運算式,以及使用類型。 不論是哪一種情況,類型或物件運算式都提供介面抽象方法的方法主體。 實作是實作介面的每個類型所專用。 因此,不同類型上的介面方法可能會彼此不同。
當您使用輕量型語法時,關鍵字 interface
和 end
會選擇性標記定義的開始和結尾。 如果您不使用這些關鍵字,編譯器會藉由分析您使用的建構,嘗試推斷類型是否為類別或介面。 如果您定義成員或使用其他類別語法,類型會解譯為類別。
.NET 編碼樣式是使用大寫 I
來開始進行所有介面。
您可以透過兩種方式指定多個參數:F#-style 和 .NET-style。 這兩者都會以相同的方式為 .NET 取用者進行編譯,但 F#-style 會強制 F# 呼叫端使用 F#-style 參數應用程式,而 .NET-style 會強制 F# 呼叫端使用元組的引數應用程式。
type INumericFSharp =
abstract Add: x: int -> y: int -> int
type INumericDotNet =
abstract Add: x: int * y: int -> int
使用類別類型實作介面
您可以使用 interface
關鍵字、介面名稱和 with
關鍵字,後面接著介面成員定義,在類別類型中實作一或多個介面,如下列程式碼所示。
type IPrintable =
abstract member Print: unit -> unit
type SomeClass1(x: int, y: float) =
interface IPrintable with
member this.Print() = printfn "%d %f" x y
介面實作是繼承的,因此任何衍生類別都不需要重新實作介面。
呼叫介面方法
介面方法只能透過介面呼叫,而不能透過實作介面的任何類型物件呼叫。 因此,您可能必須使用 :>
運算子或 upcast
運算子向上轉型至介面類型,才能呼叫這些方法。
若要在具有類型 SomeClass
的物件時呼叫介面方法,您必須將物件向上轉型至介面類型,如下列程式碼所示。
let x1 = new SomeClass1(1, 2.0)
(x1 :> IPrintable).Print()
替代方法是在物件上宣告方法,該物件會向上轉型並呼叫 介面方法,如下列範例所示。
type SomeClass2(x: int, y: float) =
member this.Print() = (this :> IPrintable).Print()
interface IPrintable with
member this.Print() = printfn "%d %f" x y
let x2 = new SomeClass2(1, 2.0)
x2.Print()
使用物件運算式實作介面
物件運算式提供實作介面的簡短方式。 當您不需要建立具名類型,且您只想要支援介面方法的物件,而不需要任何其他方法時,這相當有用。 物件運算式會在下列程式碼中說明。
let makePrintable (x: int, y: float) =
{ new IPrintable with
member this.Print() = printfn "%d %f" x y }
let x3 = makePrintable (1, 2.0)
x3.Print()
介面繼承
介面可以繼承自一或多個基底介面。
type Interface1 =
abstract member Method1: int -> int
type Interface2 =
abstract member Method2: int -> int
type Interface3 =
inherit Interface1
inherit Interface2
abstract member Method3: int -> int
type MyClass() =
interface Interface3 with
member this.Method1(n) = 2 * n
member this.Method2(n) = n + 100
member this.Method3(n) = n / 10
使用預設實作來實作介面
C# 支援使用預設實作來定義介面,如下所示:
using System;
namespace CSharp
{
public interface MyDim
{
public int Z => 0;
}
}
這些是直接從 F# 取用的:
open CSharp
// You can implement the interface via a class
type MyType() =
member _.M() = ()
interface MyDim
let md = MyType() :> MyDim
printfn $"DIM from C#: %d{md.Z}"
// You can also implement it via an object expression
let md' = { new MyDim }
printfn $"DIM from C# but via Object Expression: %d{md'.Z}"
您可以使用 override
覆寫預設實作,例如覆寫任何虛擬成員。
介面中沒有預設實作的任何成員仍必須明確實作。
在不同的泛型具現化上實作相同的介面
F# 支援在不同的泛型具現化上實作相同的介面,如下所示:
type IA<'T> =
abstract member Get : unit -> 'T
type MyClass() =
interface IA<int> with
member x.Get() = 1
interface IA<string> with
member x.Get() = "hello"
let mc = MyClass()
let iaInt = mc :> IA<int>
let iaString = mc :> IA<string>
iaInt.Get() // 1
iaString.Get() // "hello"