Schnittstellen (F#)

Schnittstellen geben Gruppen verwandter Member an, die von anderen Klassen implementiert werden.

Syntax

// 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

Bemerkungen

Schnittstellendeklarationen ähneln Klassendeklarationen – mit dem Unterschied, dass keine Member implementiert werden. Stattdessen sind alle Member abstrakt, was durch das Schlüsselwort abstract angezeigt wird. Für abstrakte Methoden stellen Sie keinen Methodenkörper bereit. F# kann keine standardmäßige Methodenimplementierung für eine Schnittstelle definieren, ist aber mit den von C# definierten Standardimplementierungen kompatibel. Standardimplementierungen mit dem Schlüsselwort default werden nur unterstützt, wenn Sie von einer Basisklasse erben, bei der es sich nicht um eine Schnittstelle handelt.

Der standardmäßige Zugriff für Schnittstellen lautet public.

Optional können Sie jedem Methodenparameter einen Namen in normaler F#-Syntax zuweisen:

type ISprintable =
    abstract member Print: format: string -> unit

In dem obigen Beispiel ISprintable umfasst die Methode Print einen einzigen Parameter des Typs string mit dem Namen format.

Es gibt zwei Möglichkeiten, Schnittstellen zu implementieren: über Objektausdrücke und über Typen. In beiden Fällen stellt der Typ oder der Objektausdruck Methodenkörper für abstrakte Methoden der Schnittstelle bereit. Implementierungen sind spezifisch für jeden Typ, der die Schnittstelle implementiert. Daher können sich die Schnittstellenmethoden verschiedener Typen voneinander unterscheiden.

Die Schlüsselwörter interface und end, die den Anfang und das Ende der Definition markieren, sind optional, wenn Sie die einfache Syntax verwenden. Wenn Sie diese Schlüsselwörter nicht verwenden, versucht der Compiler anhand der von Ihnen verwendeten Konstrukte abzuleiten, ob der Typ eine Klasse oder eine Schnittstelle ist. Wenn Sie einen Member definieren oder eine andere Klassensyntax verwenden, wird der Typ als eine Klasse interpretiert.

Der .NET-Codierungsstil sieht vor, dass alle Schnittstellen mit dem Großbuchstaben I beginnen.

Sie können mehrere Parameter auf zwei Arten angeben: Im F#-Stil und im .NET-Stil. Die Kompilierung erfolgt für .NET-Konsumenten in beiden Fällen gleich, aber der F#-Stil zwingt F#-Aufrufer zur Verwendung von Parametern im F#-Stil, und der .NET-Stil zwingt F#-Aufrufer zur Verwendung einer Anwendung mit Tupelargumenten.

type INumericFSharp =
    abstract Add: x: int -> y: int -> int

type INumericDotNet =
    abstract Add: x: int * y: int -> int

Implementierung von Schnittstellen über Klassentypen

Sie können eine oder mehrere Schnittstellen in einem Klassentyp implementieren, indem Sie das Schlüsselwort interface, den Namen der Schnittstelle und das Schlüsselwort with verwenden, gefolgt von den Definitionen der Schnittstellenmember, wie im folgenden Code gezeigt.

type IPrintable =
    abstract member Print: unit -> unit

type SomeClass1(x: int, y: float) =
    interface IPrintable with
        member this.Print() = printfn "%d %f" x y

Schnittstellenimplementierungen werden vererbt, sodass alle abgeleiteten Klassen sie nicht erneut implementieren müssen.

Aufrufen von Schnittstellenmethoden

Schnittstellenmethoden können nur über die Schnittstelle aufgerufen werden, nicht über ein Objekt des Typs, der die Schnittstelle implementiert. Daher müssen Sie den Schnittstellentyp möglicherweise mit dem :>- oder dem upcast-Operator in eine Basisklasse umwandeln, um diese Methoden aufrufen zu können.

Um bei Vorliegen eines Objekts vom Typ SomeClass die Schnittstellenmethode aufzurufen, müssen Sie das Objekt in den Schnittstellentyp umwandeln, wie im folgenden Code gezeigt.

let x1 = new SomeClass1(1, 2.0)
(x1 :> IPrintable).Print()

Eine Alternative besteht darin, eine Methode für das Objekt zu deklarieren, die die Schnittstellenmethode in eine Basisklasse umwandelt und aufruft, wie im folgenden Beispiel gezeigt:

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()

Implementieren von Schnittstellen über Objektausdrücke

Objektausdrücke bieten eine einfache Möglichkeit, eine Schnittstelle zu implementieren. Sie sind nützlich, wenn Sie keinen benannten Typ erstellen müssen, sondern nur ein Objekt benötigen, das die Methoden der Schnittstelle unterstützt (ohne zusätzliche Methoden). Ein Objektausdruck wird im folgenden Code veranschaulicht.

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()

Schnittstellenvererbung

Schnittstellen können von einer oder mehreren Basisschnittstellen erben.

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

Implementieren von Schnittstellen mit Standardimplementierungen

C# unterstützt die Definition von Schnittstellen mit Standardimplementierungen, zum Beispiel so:

using System;

namespace CSharp
{
    public interface MyDim
    {
        public int Z => 0;
    }
}

Diese können direkt aus F# genutzt werden:

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

Sie können eine Standardimplementierung mit override überschreiben, so wie Sie jeden virtuellen Member überschreiben können.

Alle Member einer Schnittstelle, für die es keine Standardimplementierung gibt, müssen weiterhin explizit implementiert werden.

Implementieren derselben Schnittstelle in verschiedenen generischen Instanzen

F# unterstützt die Implementierung derselben Schnittstelle in verschiedenen generischen Instanzen wie folgt:

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"

Weitere Informationen