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"