介面 (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 單一參數。

有兩種方式可以實作介面:使用物件運算式,以及使用類型。 不論是哪一種情況,類型或物件運算式都提供介面抽象方法的方法主體。 實作是實作介面的每個類型所專用。 因此,不同類型上的介面方法可能會彼此不同。

當您使用輕量型語法時,關鍵字 interfaceend 會選擇性標記定義的開始和結尾。 如果您不使用這些關鍵字,編譯器會藉由分析您使用的建構,嘗試推斷類型是否為類別或介面。 如果您定義成員或使用其他類別語法,類型會解譯為類別。

.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"

另請參閱