Partage via


Extensions de type

Les extensions de type (également appelées augmentations) sont une famille de fonctionnalités qui vous permettent d’ajouter de nouveaux membres à un type d’objet déjà défini. Les trois fonctionnalités sont les suivantes :

  • Extensions de type intrinsèque
  • Extensions de type facultatif
  • Méthodes d’extension

Chacune de ces fonctionnalités peut être utilisée dans différents scénarios et présente différents compromis.

Syntaxe

// Intrinsic and optional extensions
type typename with
    member self-identifier.member-name =
        body
    ...

// Extension methods
open System.Runtime.CompilerServices

[<Extension>]
type Extensions() =
    [<Extension>]
    static member extension-name (ty: typename, [args]) =
        body
    ...

Extensions de type intrinsèque

Une extension de type intrinsèque est une extension de type qui étend un type défini par l’utilisateur.

Les extensions de type intrinsèque doivent être définies dans le même fichier et dans le même espace de noms ou module que le type qu’elles étendent. Pour toute autre définition, il s’agit d’extensions de type facultatif.

Les extensions de type intrinsèque sont parfois un moyen plus propre de séparer les fonctionnalités de la déclaration de type. L’exemple suivant montre comment définir une extension de type intrinsèque :

namespace Example

type Variant =
    | Num of int
    | Str of string
  
module Variant =
    let print v =
        match v with
        | Num n -> printf "Num %d" n
        | Str s -> printf "Str %s" s

// Add a member to Variant as an extension
type Variant with
    member x.Print() = Variant.print x

L’utilisation d’une extension de type vous permet de séparer chacun des éléments suivants :

  • Déclaration d’un type Variant
  • Fonctionnalité permettant d’afficher la classe Variant en fonction de sa "forme"
  • Moyen d’accéder à la fonctionnalité d’affichage avec la notation à base de . utilisée avec les objets

Il s’agit d’une alternative qui permet d’éviter de tout définir en tant que membre sur Variant. Bien qu’il ne s’agisse pas d’une meilleure approche en soi, elle peut constituer une représentation plus nette des fonctionnalités dans certaines situations.

Les extensions de type intrinsèque sont compilées en tant que membres du type qu’elles augmentent, et apparaissent dans le type quand celui-ci est examiné par réflexion.

Extensions de type facultatif

Une extension de type facultatif est une extension qui apparaît en dehors du module, de l’espace de noms ou de l’assembly d’origine du type étendu.

Les extensions de type facultatif permettent d’étendre un type que vous n’avez pas défini vous-même. Par exemple :

module Extensions

type IEnumerable<'T> with
    /// Repeat each element of the sequence n times
    member xs.RepeatElements(n: int) =
        seq {
            for x in xs do
                for _ in 1 .. n -> x
        }

Vous pouvez désormais accéder à RepeatElements comme s’il était membre de IEnumerable<T>, tant que le module Extensions est ouvert dans l’étendue que vous utilisez.

Les extensions facultatives n’apparaissent pas dans le type étendu quand il est examiné par réflexion. Les extensions facultatives doivent se trouver dans des modules. Elles sont présentes dans l’étendue uniquement quand le module qui contient l’extension est ouvert ou qu’il fait partie de l’étendue.

Les membres d’extension facultatifs sont compilés en membres statiques pour lesquels l’instance d’objet est passée implicitement en tant que premier paramètre. Toutefois, ils se comportent comme s’ils étaient des membres d’instance ou des membres statiques, selon la façon dont ils sont déclarés.

Les membres d’extension facultatifs ne sont pas non plus visibles par les consommateurs C# ou Visual Basic. Ils peuvent uniquement être consommés dans du code F# distinct.

Limitation générique des extensions de type intrinsèque et de type facultatif

Il est possible de déclarer une extension de type sur un type générique où la variable de type est contrainte. Il est impératif que la contrainte de la déclaration d’extension corresponde à la contrainte du type déclaré.

Toutefois, même en cas de correspondance des contraintes entre un type déclaré et une extension de type, il est possible qu’une contrainte soit déduite par le corps d’un membre étendu qui impose au paramètre de type une condition différente de celle du type déclaré. Par exemple :

open System.Collections.Generic

// NOT POSSIBLE AND FAILS TO COMPILE!
//
// The member 'Sum' has a different requirement on 'T than the type IEnumerable<'T>
type IEnumerable<'T> with
    member this.Sum() = Seq.sum this

Il n’existe aucun moyen de faire fonctionner ce code avec une extension de type facultatif :

  • En l’état, le membre Sum a une contrainte sur 'T (static member get_Zero et static member (+)) qui diffère de celle définie par l’extension de type.
  • La modification de l’extension de type pour avoir la même contrainte que Sum ne correspond plus à la contrainte définie sur IEnumerable<'T>.
  • Le changement de member this.Sum en member inline this.Sum génère une erreur indiquant que les contraintes de type ne correspondent pas.

Ce qui est souhaité, ce sont des méthodes statiques qui "flottent dans l’espace", et qui peuvent être présentées comme si elles étendaient un type. C’est là que les méthodes d’extension deviennent nécessaires.

Méthodes d’extension

Enfin, les méthodes d’extension (parfois appelées "membres d’extension de style C#") peuvent être déclarées en F# en tant que méthodes de membres statiques sur une classe.

Les méthodes d’extension sont utiles quand vous souhaitez définir des extensions sur un type générique qui doit contraindre la variable de type. Par exemple :

namespace Extensions

open System.Collections.Generic
open System.Runtime.CompilerServices

[<Extension>]
type IEnumerableExtensions =
    [<Extension>]
    static member inline Sum(xs: IEnumerable<'T>) = Seq.sum xs

Quand il est utilisé, ce code donne l’impression que Sum est défini sur IEnumerable<T>, du moment que Extensions est ouvert ou qu’il se trouve dans l’étendue.

Si vous souhaitez rendre l’extension disponible pour du code VB.NET, un ExtensionAttribute supplémentaire est nécessaire au niveau de l’assembly :

module AssemblyInfo
open System.Runtime.CompilerServices
[<assembly:Extension>]
do ()

Autres remarques

Les extensions de type ont également les attributs suivants :

  • Tout type accessible peut être étendu.
  • Les extensions de type intrinsèque et de type facultatif peuvent définir n’importe quel type de membre, pas seulement des méthodes. Ainsi, les propriétés d’extension sont également possibles, par exemple.
  • Le jeton self-identifier dans la syntaxe représente l’instance du type appelé, tout comme les membres ordinaires.
  • Les membres étendus peuvent être des membres statiques ou des membres d’instance.
  • Les variables de type d’une extension de type doivent correspondre aux contraintes du type déclaré.

Les limitations suivantes existent également pour les extensions de type :

  • Les extensions de type ne prennent pas en charge les méthodes virtuelles ou abstraites.
  • Les extensions de type ne prennent pas en charge les méthodes de substitution en tant qu’augmentations.
  • Les extensions de type ne prennent pas en charge les paramètres de type résolus de manière statique.
  • Les extensions de type facultatif ne prennent pas en charge les constructeurs en tant qu’augmentations.
  • Les extensions de type ne peuvent pas être définies sur des abréviations de type.
  • Les extensions de type ne sont pas valides pour byref<'T> (bien qu’elles puissent être déclarées).
  • Les extensions de type ne sont pas valides pour les attributs (bien qu’elles puissent être déclarées).
  • Vous pouvez définir des extensions qui surchargent d’autres méthodes portant le même nom, mais le compilateur F# privilégie les méthodes qui ne sont pas des méthodes d’extension en cas d’appel ambigu.

Enfin, s’il existe plusieurs extensions de type intrinsèque pour un seul type, tous les membres doivent être uniques. Pour les extensions de type facultatif, les membres d’autres extensions de type qui ciblent le même type peuvent avoir les mêmes noms. Les erreurs d’ambiguïté se produisent uniquement si le code client ouvre deux étendues différentes qui définissent les mêmes noms de membres.

Voir aussi