Partager 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 précédemment défini. Les trois fonctionnalités sont les suivantes :

  • Extensions de type intrinsèques
  • Extensions de type facultatives
  • Méthodes d’extension

Chacun peut être utilisé dans différents scénarios et a des compromis différents.

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èques

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 le même module que le type qu’ils étendent. Toute autre définition entraîne l’ajout d’extensions de type facultatives.

Les extensions de type intrinsèques 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 chacune des options suivantes :

  • Déclaration d’un Variant type
  • Fonctionnalité permettant d’imprimer la Variant classe en fonction de sa « forme »
  • Un moyen d’accéder à la fonctionnalité d’impression avec la notation de style .objet

Il s’agit d’une alternative à la définition de tout en tant que membre sur Variant. Bien qu’il ne s’agit pas d’une approche intrinsèquement meilleure, il peut s’agir d’une représentation plus claire des fonctionnalités dans certaines situations.

Les extensions de type intrinsèques sont compilées en tant que membres du type qu’elles augmentent et apparaissent sur le type lorsque le type est examiné par réflexion.

Extensions de type facultatives

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

Les extensions de type facultatives sont utiles pour é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 s’agit d’un membre tant IEnumerable<T> que le Extensions module est ouvert dans l’étendue dans laquelle vous travaillez.

Les extensions facultatives n’apparaissent pas sur le type étendu lorsqu’elles sont examinées par réflexion. Les extensions facultatives doivent être dans les modules et elles sont uniquement dans l’étendue lorsque le module qui contient l’extension est ouvert ou est dans 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 agissent comme s’ils sont des membres d’instance ou des membres statiques en fonction de 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 ne peuvent être consommés que dans d’autres codes F#.

Limitation générique des extensions de type intrinsèques et facultatives

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

Toutefois, même si des contraintes sont mises en correspondance 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 une exigence différente du paramètre de type que le 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 d’obtenir ce code pour qu’il fonctionne avec une extension de type facultative :

  • Comme c’est le cas, le Sum membre a une contrainte différente sur 'T (static member get_Zero et static member (+)) de ce que définit l’extension de type.
  • La modification de l’extension de type pour avoir la même contrainte que Sum celle-ci ne correspond plus à la contrainte définie sur IEnumerable<'T>.
  • member inline this.Sum La modification member this.Sum donne une erreur indiquant que les contraintes de type sont incompatibles.

Ce qui est souhaité est des méthodes statiques qui « flottent dans l’espace » et peuvent être présentées comme s’ils étendent 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# comme méthode membre statique sur une classe.

Les méthodes d’extension sont utiles lorsque vous souhaitez définir des extensions sur un type générique qui limite 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

Lorsqu’il est utilisé, ce code apparaît comme s’il Sum est défini sur IEnumerable<T>, tant qu’il Extensions a été ouvert ou est dans l’étendue.

Pour que l’extension soit disponible pour VB.NET code, un supplément ExtensionAttribute est requis 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 est étendu.
  • Les extensions de type intrinsèques et facultatives peuvent définir n’importe quel type de membre, pas seulement les méthodes. Ainsi, les propriétés d’extension sont également possibles, par exemple.
  • Le self-identifier jeton de la syntaxe représente l’instance du type appelé, tout comme les membres ordinaires.
  • Les membres étendus peuvent être des membres statiques ou d’instance.
  • Les variables de type sur 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 remplacement comme augmentations.
  • Les extensions de type ne prennent pas en charge les paramètres de type résolus statiquement.
  • Les extensions de type facultatives ne prennent pas en charge les constructeurs comme des augmentations.
  • Les extensions de type ne peuvent pas être définies sur les 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 du même nom, mais le compilateur F# donne la préférence aux méthodes non-extension s’il existe un appel ambigu.

Enfin, si plusieurs extensions de type intrinsèque existent pour un seul type, tous les membres doivent être uniques. Pour les extensions de type facultatives, les membres de différentes extensions de type au 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