Interfaces (F#)

Les interfaces spécifient des ensembles de membres associés implémentés par d’autres classes.

Syntaxe

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

Notes

Les déclarations d’interface ressemblent à des déclarations de classe, sauf qu’aucun membre n’est implémenté. Au lieu de cela, tous les membres sont abstraits, comme indiqué par le mot clé abstract. Vous ne fournissez pas de corps de méthode pour les méthodes abstraites. F# ne peut pas définir une implémentation de méthode par défaut sur une interface, mais est compatible avec les implémentations par défaut définies par C#. Les implémentations par défaut utilisant le mot clé default sont uniquement prises en charge lors de l’héritage d’une classe de base autre que l’interface.

L’accessibilité par défaut pour les interfaces est public.

Vous pouvez éventuellement attribuer un nom à chaque paramètre de méthode à l’aide de la syntaxe F# normale :

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

Dans l’exemple ISprintable ci-dessus, la méthode Print a un seul paramètre du type string avec le nom format.

Il existe deux façons d’implémenter des interfaces : avec des expressions d’objet et avec des types. Dans les deux cas, le type ou l’expression d’objet fournit des corps de méthode pour les méthodes abstraites de l’interface. Les implémentations sont spécifiques à chaque type qui implémente l’interface. Par conséquent, les méthodes d’interface sur différents types peuvent être différentes les unes des autres.

Les mots clés interface et end, qui marquent le début et la fin de la définition, sont facultatifs lorsque vous utilisez une syntaxe légère. Si vous n’utilisez pas ces mots clés, le compilateur tente de déduire si le type est une classe ou une interface en analysant les constructions que vous utilisez. Si vous définissez un membre ou utilisez une autre syntaxe de classe, le type est interprété comme une classe.

Le style de codage .NET consiste à commencer toutes les interfaces avec un I majuscule.

Vous pouvez spécifier plusieurs paramètres de deux manières : dans le style F# ou .NET. Les deux compilent de la même façon pour les consommateurs .NET, mais le style F# force les appelants F# à utiliser l’application de paramètre de style F#, et le style .NET force les appelants F# à utiliser l’application d’arguments tuplés.

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

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

Implémentation d’interfaces à l’aide de types de classes

Vous pouvez implémenter une ou plusieurs interfaces dans un type de classe à l’aide du mot clé interface, du nom de l’interface et du mot clé with, suivis des définitions de membre de l’interface, comme indiqué dans le code suivant.

type IPrintable =
    abstract member Print: unit -> unit

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

Les implémentations d’interface étant héritées, les classes dérivées n’ont pas besoin de les réimplémenter.

Méthodes d’interface d’appel

Les méthodes d’interface peuvent être appelées uniquement par le biais de l’interface, et non par n’importe quel objet du type qui implémente l’interface. Par conséquent, vous devrez peut-être effectuer un upcast vers le type d’interface à l’aide de l’opérateur :> ou de l’opérateur upcast pour appeler ces méthodes.

Pour appeler la méthode d’interface lorsque vous avez un objet de type SomeClass, vous devez le faire passer par un upcast vers le type d’interface, comme indiqué dans le code suivant.

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

Une alternative consiste à déclarer une méthode sur l’objet qui effectue l’upcast et appelle la méthode d’interface, comme dans l’exemple suivant.

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

Implémentation d’interfaces à l’aide d’expressions d’objet

Les expressions d’objet fournissent un moyen court d’implémenter une interface. Elles sont utiles quand vous n’avez pas besoin de créer un type nommé et que vous souhaitez simplement un objet qui prend en charge les méthodes d’interface, sans méthodes supplémentaires. Une expression d’objet est illustrée dans le code suivant.

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

Héritage de l'interface

Les interfaces peuvent hériter d’une ou de plusieurs interfaces de 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

Implémentation d’interfaces avec des implémentations par défaut

C# prend en charge la définition d’interfaces avec des implémentations par défaut, comme suit :

using System;

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

Celles-ci sont directement consommables à partir de 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}"

Vous pouvez remplacer une implémentation par défaut par override, par exemple pour remplacer n’importe quel membre virtuel.

Tous les membres d’une interface qui n’ont pas d’implémentation par défaut doivent toujours être implémentés explicitement.

Implémentation de la même interface à différentes instanciations génériques

F# prend en charge l’implémentation de la même interface à différentes instanciations génériques, comme suit :

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"

Voir aussi