Interfacce (F#)
Le interfacce specificano set di membri correlati implementati da altre classi.
Sintassi
// 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
Osservazioni:
Le dichiarazioni di interfaccia sono simili alle dichiarazioni di classe, ad eccezione del fatto che non vengono implementati membri. Tutti i membri sono invece astratti, come indicato dalla parola chiave abstract
. Non si fornisce un corpo del metodo per i metodi astratti. F# non può definire un'implementazione predefinita del metodo in un'interfaccia, ma è compatibile con le implementazioni predefinite definite da C#. Le implementazioni predefinite che usano la default
parola chiave sono supportate solo quando ereditano da una classe di base non interfaccia.
L'accessibilità predefinita per le interfacce è public
.
Facoltativamente, è possibile assegnare a ogni parametro di metodo un nome usando la sintassi F# normale:
type ISprintable =
abstract member Print: format: string -> unit
Nell'esempio precedente ISprintable
il Print
metodo ha un singolo parametro del tipo string
con il nome format
.
Esistono due modi per implementare le interfacce: usando espressioni di oggetto e tramite tipi. In entrambi i casi, il tipo o l'espressione oggetto fornisce corpi di metodo per i metodi astratti dell'interfaccia. Le implementazioni sono specifiche di ogni tipo che implementa l'interfaccia . Di conseguenza, i metodi di interfaccia su tipi diversi potrebbero essere diversi l'uno dall'altro.
Le parole chiave interface
e end
, che contrassegnano l'inizio e la fine della definizione, sono facoltative quando si usa la sintassi leggera. Se non si usano queste parole chiave, il compilatore tenta di dedurre se il tipo è una classe o un'interfaccia analizzando i costrutti usati. Se si definisce un membro o si usa un'altra sintassi di classe, il tipo viene interpretato come una classe.
Lo stile di codifica .NET consiste nell'iniziare tutte le interfacce con un maiuscolo I
.
È possibile specificare più parametri in due modi: F#-style e . Stile NET. Entrambi compileranno lo stesso modo per i consumer .NET, ma F#-style forza i chiamanti F# a usare l'applicazione di parametri F#-style e . Lo stile NET forza i chiamanti F# a usare l'applicazione di argomenti tupled.
type INumericFSharp =
abstract Add: x: int -> y: int -> int
type INumericDotNet =
abstract Add: x: int * y: int -> int
Implementazione di interfacce tramite tipi di classe
È possibile implementare una o più interfacce in un tipo di classe usando la interface
parola chiave , il nome dell'interfaccia e la with
parola chiave , seguita dalle definizioni dei membri dell'interfaccia, come illustrato nel codice seguente.
type IPrintable =
abstract member Print: unit -> unit
type SomeClass1(x: int, y: float) =
interface IPrintable with
member this.Print() = printfn "%d %f" x y
Le implementazioni dell'interfaccia vengono ereditate, quindi tutte le classi derivate non devono essere riapplicate.
Metodi di interfaccia di chiamata
I metodi di interfaccia possono essere chiamati solo tramite l'interfaccia, non tramite qualsiasi oggetto del tipo che implementa l'interfaccia. Pertanto, potrebbe essere necessario eseguire il upcast al tipo di interfaccia usando l'operatore :>
o l'operatore upcast
per chiamare questi metodi.
Per chiamare il metodo di interfaccia quando si dispone di un oggetto di tipo SomeClass
, è necessario eseguire il upcast dell'oggetto al tipo di interfaccia, come illustrato nel codice seguente.
let x1 = new SomeClass1(1, 2.0)
(x1 :> IPrintable).Print()
Un'alternativa consiste nel dichiarare un metodo sull'oggetto che esegue l'upcast e chiama il metodo di interfaccia, come nell'esempio seguente.
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()
Implementazione di interfacce tramite espressioni di oggetto
Le espressioni di oggetto consentono di implementare un'interfaccia in breve tempo. Sono utili quando non è necessario creare un tipo denominato e si vuole solo un oggetto che supporta i metodi di interfaccia, senza metodi aggiuntivi. Un'espressione di oggetto è illustrata nel codice seguente.
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()
Ereditarietà dell'interfaccia
Le interfacce possono ereditare da una o più interfacce di base.
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
Implementazione di interfacce con implementazioni predefinite
C# supporta la definizione di interfacce con implementazioni predefinite, ad esempio:
using System;
namespace CSharp
{
public interface MyDim
{
public int Z => 0;
}
}
Questi sono direttamente utilizzabili da 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}"
È possibile eseguire l'override di un'implementazione predefinita con override
, come l'override di qualsiasi membro virtuale.
Tutti i membri di un'interfaccia che non dispongono di un'implementazione predefinita devono comunque essere implementati in modo esplicito.
Implementazione della stessa interfaccia in istanze generiche diverse
F# supporta l'implementazione della stessa interfaccia in istanze generiche diverse, ad esempio:
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"