Méthodes d'extension (Visual Basic)

Avec les méthodes d’extension, les développeurs peuvent ajouter des fonctionnalités personnalisées aux types de données déjà définis sans avoir à créer de type dérivé. Les méthodes d’extension permettent d’écrire une méthode qui peut être appelée comme s’il s’agissait d’une méthode d’instance du type existant.

Notes

Une méthode d’extension ne peut être qu’une procédure Sub ou une procédure Function. Vous ne pouvez pas définir une propriété, un champ ou un événement comme méthode d’extension. Toutes les méthodes d’extension doivent être marquées avec l’attribut d’extension <Extension> de l’espace de noms System.Runtime.CompilerServices, et être définies dans un module. Si une méthode d’extension est définie en dehors d’un module, le compilateur Visual Basic génère l’erreur BC36551, « Les méthodes d’extension peuvent être définies uniquement dans des modules ».

Le premier paramètre dans une définition de méthode d’extension spécifie le type de données que la méthode étend. Quand la méthode est exécutée, le premier paramètre est lié à l’instance du type de données qui appelle la méthode.

L’attribut Extension ne peut être appliqué qu’à un Module, Sub ou Function Visual Basic. Si vous l’appliquez à un Class ou un Structure, le compilateur Visual Basic génère l’erreur BC36550, « L’attribut Extension peut être appliqué uniquement aux déclarations 'Module', 'Sub' ou 'Function' ».

Exemple

L’exemple suivant définit une extension Print du type de données String. La méthode utilise Console.WriteLine pour afficher une chaîne. Le paramètre de la méthode Print, aString, établit que la méthode étend la classe String.

Imports System.Runtime.CompilerServices

Module StringExtensions

    <Extension()> 
    Public Sub Print(ByVal aString As String)
        Console.WriteLine(aString)
    End Sub

End Module

Notez que la définition de la méthode d’extension est marquée avec l’attribut d’extension <Extension()>. Le marquage du module où la méthode est définie est facultatif, mais chaque méthode d’extension doit être marquée. System.Runtime.CompilerServices doit être importé pour permettre l’accès à l’attribut d’extension.

Les méthodes d’extension peuvent être déclarées dans des modules uniquement. En règle générale, le module où une méthode d’extension est définie n’est pas le même module que celui dans lequel elle est appelée. Au lieu de cela, le module qui contient la méthode d’extension est importé, si nécessaire, pour l’inclure dans l’étendue. Une fois que le module contenant Print est dans l’étendue, la méthode peut être appelée comme s’il s’agissait d’une méthode d’instance ordinaire qui ne prend aucun argument, comme ToUpper :

Module Class1

    Sub Main()

        Dim example As String = "Hello"
        ' Call to extension method Print.
        example.Print()

        ' Call to instance method ToUpper.
        example.ToUpper()
        example.ToUpper.Print()

    End Sub

End Module

L’exemple suivant, PrintAndPunctuate, est également une extension de String, qui est cette fois définie avec deux paramètres. Le premier paramètre, aString, établit que la méthode d’extension étend String. Le second paramètre, punc, est destiné à être une chaîne de signes de ponctuation qui est passée comme argument quand la méthode est appelée. La méthode affiche la chaîne suivie des signes de ponctuation.

<Extension()> 
Public Sub PrintAndPunctuate(ByVal aString As String, 
                             ByVal punc As String)
    Console.WriteLine(aString & punc)
End Sub

La méthode est appelée en envoyant un argument de chaîne pour punc : example.PrintAndPunctuate(".")

L’exemple suivant montre Print et PrintAndPunctuate qui sont définis et appelés. System.Runtime.CompilerServices est importé dans le module de définition pour permettre l’accès à l’attribut d’extension.

Imports System.Runtime.CompilerServices

Module StringExtensions

    <Extension()>
    Public Sub Print(aString As String)
        Console.WriteLine(aString)
    End Sub

    <Extension()>
    Public Sub PrintAndPunctuate(aString As String, punc As String)
        Console.WriteLine(aString & punc)
    End Sub
End Module

Ensuite, les méthodes d’extension sont incluses dans l’étendue et appelées :

Imports ConsoleApplication2.StringExtensions

Module Module1

    Sub Main()
        Dim example As String = "Example string"
        example.Print()

        example = "Hello"
        example.PrintAndPunctuate(".")
        example.PrintAndPunctuate("!!!!")
    End Sub
End Module

La seule exigence pour pouvoir exécuter ces méthodes d’extension, ou des méthodes d’extension similaires, est qu’elles soient dans l’étendue. Si le module qui contient une méthode d’extension est dans l’étendue, il est visible dans IntelliSense et peut être appelé comme s’il s’agissait d’une méthode d’instance ordinaire.

Notez que lorsque les méthodes sont appelées, aucun argument n’est envoyé au premier paramètre. Le paramètre aString dans les définitions de méthode précédentes est lié à example, l’instance de String qui les appelle. Le compilateur utilise example comme argument envoyé au premier paramètre.

Si une méthode d’extension est appelée pour un objet défini sur Nothing, la méthode d’extension s’exécute. Cela ne s’applique pas aux méthodes d’instance ordinaires. Vous pouvez explicitement rechercher Nothing dans la méthode d’extension.

Types pouvant être étendus

Vous pouvez définir une méthode d’extension sur la plupart des types qui peuvent être représentés dans une liste de paramètres Visual Basic, notamment les types suivants :

  • Classes (types référence)
  • Structures (types valeur)
  • Interfaces
  • Délégués
  • Arguments ByRef et ByVal
  • Paramètres de méthode générique
  • Tableaux

Étant donné que le premier paramètre spécifie le type de données que la méthode d’extension étend, il est obligatoire et ne peut pas devenir facultatif. Cela explique pourquoi les paramètres Optional et ParamArray ne peuvent pas être le premier paramètre de la liste des paramètres.

Les méthodes d’extension ne sont pas prises en compte dans les liaisons tardives. Dans l’exemple suivant, l’instruction anObject.PrintMe() lève une exception MissingMemberException, à savoir la même exception que celle que vous verriez si la deuxième définition de méthode d’extension, PrintMe, avait été supprimée.

Option Strict Off
Imports System.Runtime.CompilerServices

Module Module4

    Sub Main()
        Dim aString As String = "Initial value for aString"
        aString.PrintMe()

        Dim anObject As Object = "Initial value for anObject"
        ' The following statement causes a run-time error when Option
        ' Strict is off, and a compiler error when Option Strict is on.
        'anObject.PrintMe()
    End Sub

    <Extension()> 
    Public Sub PrintMe(ByVal str As String)
        Console.WriteLine(str)
    End Sub

    <Extension()> 
    Public Sub PrintMe(ByVal obj As Object)
        Console.WriteLine(obj)
    End Sub

End Module

Bonnes pratiques

Les méthodes d’extension offrent un moyen pratique et puissant d’étendre un type existant. Toutefois, pour bien utiliser ces méthodes, il y a quelques points à prendre en considération. Ces considérations s’appliquent principalement aux auteurs de bibliothèques de classes, mais elles peuvent concerner les applications qui utilisent des méthodes d’extension.

En règle générale, les méthodes d’extension que vous ajoutez à des types dont vous n’êtes pas propriétaire sont plus vulnérables que les méthodes d’extension ajoutées à des types qui sont sous votre contrôle. Plusieurs choses susceptibles d’interférer avec vos méthodes d’extension peuvent se produire dans les classes qui ne vous appartiennent pas.

  • S’il existe un membre d’instance accessible qui a une signature compatible avec les arguments dans l’instruction appelante, sans conversions restrictives requises d’argument en paramètre, la méthode d’instance est utilisée de préférence à une méthode d’extension. Par conséquent, si une méthode d’instance appropriée est ajoutée à une classe à un moment donné, un membre d’extension existant qui doit être utilisé peut devenir inaccessible.

  • L’auteur d’une méthode d’extension ne peut pas empêcher d’autres programmeurs d’écrire des méthodes d’extension qui sont potentiellement en conflit et prioritaires sur l’extension d’origine.

  • Vous pouvez améliorer la robustesse en plaçant les méthodes d’extension dans leur propre espace de noms. Les consommateurs de votre bibliothèque peuvent alors inclure ou exclure un espace de noms, ou sélectionner des espaces de noms, séparément du reste de la bibliothèque.

  • Il est parfois préférable d’étendre des interfaces que d’étendre des classes, en particulier si vous n’êtes pas propriétaire de l’interface ou de la classe. Toute modification dans une interface impacte chaque classe qui implémente l’interface. L’auteur est donc moins susceptible d’ajouter ou de modifier des méthodes dans une interface. En revanche, si une classe implémente deux interfaces qui ont des méthodes d’extension avec la même signature, aucune de ces méthodes d’extension n’est visible.

  • Essayez d’étendre le type qui est le plus spécifique. Dans une hiérarchie de types, si vous sélectionnez un type duquel dérivent beaucoup d’autres types, l’introduction de méthodes d’instance ou d’autres méthodes d’extension peut entraîner des interférences à plusieurs niveaux avec les vôtres.

Méthodes d’extension, méthodes d’instance et propriétés

Quand une méthode d’instance dans l’étendue a une signature qui est compatible avec les arguments d’une instruction appelante, cette méthode d’instance est choisie de préférence à toute méthode d’extension. La méthode d’instance est prioritaire même si la méthode d’extension convient mieux. Dans l’exemple suivant, ExampleClass contient une méthode d’instance nommée ExampleMethod qui a un paramètre de type Integer. La méthode d’extension ExampleMethod étend ExampleClass et a un paramètre de type Long.

Class ExampleClass
    ' Define an instance method named ExampleMethod.
    Public Sub ExampleMethod(ByVal m As Integer)
        Console.WriteLine("Instance method")
    End Sub
End Class

<Extension()> 
Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal n As Long)
    Console.WriteLine("Extension method")
End Sub

Le premier appel de ExampleMethod dans le code suivant appelle la méthode d’extension, car arg1 est Long et est compatible uniquement avec le paramètre Long dans la méthode d’extension. Le deuxième appel de ExampleMethod a un argument Integer, arg2, et appelle la méthode d’instance.

Sub Main()
    Dim example As New ExampleClass
    Dim arg1 As Long = 10
    Dim arg2 As Integer = 5

    ' The following statement calls the extension method.
    example.exampleMethod(arg1)
    ' The following statement calls the instance method.
    example.exampleMethod(arg2)
End Sub

À présent, inversez les types de données des paramètres dans les deux méthodes :

Class ExampleClass
    ' Define an instance method named ExampleMethod.
    Public Sub ExampleMethod(ByVal m As Long)
        Console.WriteLine("Instance method")
    End Sub
End Class

<Extension()> 
Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal n As Integer)
    Console.WriteLine("Extension method")
End Sub

Cette fois, le code dans Main appelle la méthode d’instance les deux fois. Cela est dû au fait que arg1 et arg2 ont une conversion étendue en Long, et que la méthode d’instance est prioritaire sur la méthode d’extension dans les deux cas.

Sub Main()
    Dim example As New ExampleClass
    Dim arg1 As Long = 10
    Dim arg2 As Integer = 5

    ' The following statement calls the instance method.
    example.ExampleMethod(arg1)
    ' The following statement calls the instance method.
    example.ExampleMethod(arg2)
End Sub

Une méthode d’extension ne peut donc pas remplacer une méthode d’instance existante. Toutefois, quand une méthode d’extension porte le même nom qu’une méthode d’instance, mais que les signatures ne sont pas en conflit, les deux méthodes sont accessibles. Par exemple, si la classe ExampleClass contient une méthode nommée ExampleMethod qui ne prend aucun argument, les méthodes d’extension de même nom qui ont des signatures différentes sont autorisées, comme le montre le code suivant.

Imports System.Runtime.CompilerServices

Module Module3

    Sub Main()
        Dim ex As New ExampleClass
        ' The following statement calls the extension method.
        ex.ExampleMethod("Extension method")
        ' The following statement calls the instance method.
        ex.ExampleMethod()
    End Sub

    Class ExampleClass
        ' Define an instance method named ExampleMethod.
        Public Sub ExampleMethod()
            Console.WriteLine("Instance method")
        End Sub
    End Class

    <Extension()> 
    Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal stringParameter As String)
        Console.WriteLine(stringParameter)
    End Sub

End Module

Voici la sortie de ce code :

Extension method
Instance method

La situation est plus simple avec les propriétés : si une méthode d’extension porte le même nom qu’une propriété de la classe qu’elle étend, la méthode d’extension n’est ni visible ni accessible.

Priorité entre les méthodes d’extension

Si deux méthodes d’extension avec des signatures identiques sont dans l’étendue et sont accessibles, celle ayant la priorité la plus haute est appelée. La priorité d’une méthode d’extension est déterminée par le mécanisme utilisé pour inclure la méthode dans l’étendue. La liste suivante présente la hiérarchie des priorités, de la plus haute à la plus basse.

  1. Méthodes d’extension définies dans le module actif.

  2. Méthodes d’extension définies dans des types de données de l’espace de noms actuel ou de l’un de ses parents, sachant que les espaces de noms enfants ont une priorité plus haute que les espaces de noms parents.

  3. Méthodes d’extension définies dans des importations de types dans le fichier actif.

  4. Méthodes d’extension définies dans des importations d’espaces de noms dans le fichier actif.

  5. Méthodes d’extension définies dans des importations de types au niveau du projet.

  6. Méthodes d’extension définies dans des importations d’espaces de noms au niveau du projet.

Si la priorité ne permet pas de résoudre l’ambiguïté, vous pouvez spécifier la méthode à appeler par son nom complet. Si la méthode Print dans l’exemple précédent est définie dans un module nommé StringExtensions, le nom complet est StringExtensions.Print(example), et non example.Print().

Voir aussi