Erweiterungsmethoden (Visual Basic)

Erweiterungsmethoden ermöglichen Entwicklern das Hinzufügen von benutzerdefinierten Funktionen zu bereits definierten Datentypen, ohne dass ein neuer abgeleiteter Typ erstellt werden muss. Erweiterungsmethoden ermöglichen das Erstellen einer Methode, die aufgerufen werden kann, als wäre sie eine Instanzenmethode des vorhandenen Typs.

Bemerkungen

Eine Erweiterungsmethode kann ausschließlich eine Sub-Prozedur oder eine Function-Prozedur sein. Erweiterungseigenschaften, -felder oder -ereignisse können nicht definiert werden. Alle Erweiterungsmethoden müssen mit dem Erweiterungsattribut <Extension> aus dem System.Runtime.CompilerServices Namespace gekennzeichnet und in einem Moduldefiniert werden. Wenn eine Erweiterungsmethode außerhalb eines Moduls definiert ist, generiert der Visual Basic-Compiler Fehler BC36551, „Erweiterungsmethoden können nur in Modulen definiert werden“.

Der erste Parameter in der Definition einer Erweiterungsmethode gibt den Datentyp an, der von der Methode erweitert wird. Beim Ausführen der Methode wird der erste Parameter an die Instanz des Datentyps gebunden, der die Methode aufruft.

Das Extension-Attribut kann nur auf ein Visual Basic Module, Sub oder Function angewendet werden. Wenn Sie sie auf eine Class oder Structure-Anwendung anwenden, generiert der Visual Basic-Compiler Fehler BC36550, „Extension'-Attribut kann nur auf 'Module'-, 'Sub- oder 'Function'-Deklarationen angewendet werden“.

Beispiel

Im folgenden Beispiel wird eine Print-Erweiterung für den String-Datentyp definiert. Die Methode verwendet Console.WriteLine, um eine Zeichenfolge anzuzeigen. Durch den Print-Parameter der aString-Methode wird festgelegt, dass die String-Klasse von der Methode erweitert wird.

Imports System.Runtime.CompilerServices

Module StringExtensions

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

End Module

Beachten Sie, dass die Definition der Erweiterungsmethode mit dem Erweiterungsattribut <Extension()> markiert ist. Die Markierung des Moduls, in dem die Methode definiert ist, ist optional, aber jede Erweiterungsmethode muss markiert werden. Zum Zugriff auf das Erweiterungsattribut muss System.Runtime.CompilerServices importiert werden.

Erweiterungsmethoden können nur innerhalb von Modulen deklariert werden. Bei dem Modul, in dem eine Erweiterungsmethode definiert wird, handelt es sich normalerweise um ein anderes Modul als das, in dem sie aufgerufen wird. Stattdessen wird das Modul, in dem die Erweiterungsmethode enthalten ist, ggf. importiert, um es in den Gültigkeitsbereich einzubinden. Nachdem sich das Modul, in dem Print enthalten ist, im Gültigkeitsbereich befindet, kann die Methode wie jede andere gewöhnliche Instanzenmethode, die keine Argumente verwendet (z. B. ToUpper) aufgerufen werden:

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

Das nächste Beispiel, PrintAndPunctuate, ist auch eine Erweiterung für String und wird dieses Mal mit zwei Parametern definiert. Der erste Parameter, aString, legt fest, dass die Erweiterungsmethode String erweitert. Mit dem zweiten Parameter, punc, wird eine aus Satzzeichen bestehende Zeichenfolge bereitgestellt, die beim Aufruf der Methode als Argument übergeben wird. Durch die Methode wird die Zeichenfolge gefolgt von den Satzzeichen angezeigt.

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

Die Methode wird aufgerufen, indem ein Zeichenfolgenargument für punc gesendet wird: example.PrintAndPunctuate(".")

Im folgenden Beispiel werden Print und PrintAndPunctuate definiert und aufgerufen. System.Runtime.CompilerServices wird in das Definitionsmodul importiert, um den Zugriff auf das Erweiterungsattribut zu ermöglichen.

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

Als Nächstes werden die Erweiterungsmethoden in den Gültigkeitsbereich eingebunden und aufgerufen:

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

Die einzige Voraussetzung für das Ausführen dieser oder ähnlicher Erweiterungsmethoden besteht darin, dass sie sich innerhalb des Gültigkeitsbereichs befinden müssen. Wenn sich das Modul mit einer Erweiterungsmethode im Gültigkeitsbereich befindet, ist es für IntelliSense erkennbar und kann wie jede andere gewöhnliche Instanzenmethode aufgerufen werden.

Beachten Sie, dass beim Aufrufen der Methoden kein Argument für den ersten Parameter gesendet wird. Der aString-Parameter in den vorherigen Methodendefinitionen ist an example gebunden, also die Instanz von String, durch die sie aufgerufen werden. Der Compiler verwendet example als das an den ersten Parameter gesendete Argument.

Wenn eine Erweiterungsmethode für ein Objekt aufgerufen wird, das auf Nothing festgelegt ist, wird die Erweiterungsmethode ausgeführt. Dies trifft nicht auf normale Instanzmethoden zu. Sie können in der Erweiterungsmethode explizit auf Nothing überprüfen.

Erweiterungsfähige Typen

Erweiterungsmethoden können für die meisten Typen definiert werden, die in Visual Basic-Parameterlisten wie den folgenden dargestellt werden können:

  • Klassen (Referenztypen)
  • Strukturen (Werttypen)
  • Schnittstellen
  • Delegaten
  • ByRef- und ByVal-Argumente
  • Parameter für generische Methoden
  • Arrays

Da der erste Parameter den Datentyp angibt, der durch die Erweiterungsmethode erweitert wird, ist er erforderlich und kann nicht ausgelassen werden. Aus diesem Grund kann ein Optional-Parameter oder ein ParamArray-Parameter nicht der erste Parameter in der Parameterliste sein.

Erweiterungsmethoden werden bei der späten Bindung nicht berücksichtigt. Im folgenden Beispiel löst die anObject.PrintMe()-Anweisung eine MissingMemberException-Ausnahme aus. Dieselbe Ausnahme wird angezeigt, wenn die zweite PrintMe-Erweiterungsmethodendefinition gelöscht würde.

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

Bewährte Methoden

Erweiterungsmethoden bieten eine einfache und leistungsstarke Möglichkeit zur Erweiterung eines vorhandenen Typs. Um sie erfolgreich zu verwenden, sind jedoch einige Punkte zu beachten. Obwohl sich diese Überlegungen hauptsächlich auf Autoren von Klassenbibliotheken beziehen, können sie gleichzeitig Anwendungen betreffen, die Erweiterungsmethoden verwenden.

Erweiterungsmethoden, die Sie Typen hinzufügen, die sich nicht in Ihrem Besitz befinden, sind im Allgemeinen angreifbarer als Erweiterungsmethoden, die Sie Typen hinzufügen, die von Ihnen gesteuert werden. In Klassen, die sich nicht in Ihrem Besitz befinden, können einige Ereignisse auftreten, die Ihre Erweiterungsmethoden negativ beeinflussen könnten.

  • Falls ein Instanzenmember vorhanden ist, auf den zugegriffen werden kann und der über eine mit den Argumenten in der aufrufenden Anweisung kompatible Signatur verfügt, ohne dass einschränkende Konvertierungen vom Argument zum Parameter festgelegt sind, wird die Instanzenmethode vor allen Erweiterungsmethoden verwendet. Daher ist es möglich, dass auf einen vorhandenen Erweiterungsmember, den Sie verwenden möchten, nicht mehr zugegriffen werden kann, wenn einer Klasse zu einem bestimmten Zeitpunkt eine geeignete Instanzenmethode hinzugefügt wird.

  • Der Autor einer Erweiterungsmethode kann nicht verhindern, dass andere Programmierer Erweiterungsmethoden schreiben, die Vorrang vor der ursprünglichen Erweiterung haben und damit Konflikte verursachen.

  • Sie können die Stabilität verbessern, indem Sie Erweiterungsmethoden in einen eigenen Namespace einfügen. Consumer Ihrer Bibliothek können einen Namespace dann ein- oder ausschließen bzw. vom Rest der Bibliothek getrennt unter den Namespaces auswählen.

  • Das Erweitern von Schnittstellen bietet u. U. mehr Sicherheit als das Erweitern von Klassen, insbesondere, wenn Sie nicht der Besitzer der Schnittstelle oder Klasse sind. Eine Änderung einer Schnittstelle wirkt sich auf jede Klasse aus, die von ihr implementiert wird. Deshalb ist es eher unwahrscheinlich, dass der Autor Methoden in einer Schnittstelle hinzufügt oder ändert. Wenn eine Klasse jedoch zwei Schnittstellen implementiert, die über Erweiterungsmethoden mit gleicher Signatur verfügen, wird keine der Erweiterungsmethoden angezeigt.

  • Erweitern Sie einen möglichst spezifischen Typ. Wenn Sie in einer Typhierarchie einen Typ auswählen, von dem viele andere Typen abgeleitet sind, können auf viele Weisen Instanzenmethoden oder andere Erweiterungsmethoden eingeführt werden, die Konflikte mit Ihren Instanzen- oder Erweiterungsmethoden verursachen könnten.

Erweiterungsmethoden, Instanzmethoden und Eigenschaften

Wenn eine Instanzmethode im Gültigkeitsbereich über eine Signatur verfügt, die mit den Argumenten einer Aufrufanweisung kompatibel ist, wird die Instanzmethode vor den Erweiterungsmethoden bevorzugt ausgewählt. Die Instanzmethode hat auch dann Vorrang, wenn die Erweiterungsmethode eine bessere Übereinstimmung aufweist. Im folgenden Beispiel enthält die ExampleClass eine Instanzmethode mit der Bezeichnung ExampleMethod, die über einen Parameter des Typs Integer verfügt. Die Erweiterungsmethode ExampleMethod erweitert die ExampleClass und verfügt über einen Parameter des Typs 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

Mit dem ersten Aufruf an ExampleMethod im folgenden Code wird die Erweiterungsmethode aufgerufen, da arg1 den Wert Long hat und nur mit dem Long-Parameter in der Erweiterungsmethode kompatibel ist. Der zweite Aufruf von ExampleMethod verfügt über ein Integer-Argument, arg2, und es ruft die Instanzmethode auf.

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

Kehren Sie nun die Datentypen der Parameter in den zwei Methoden um:

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

Dieses Mal ruft der Code in Main beide Male die Instanzmethode auf. Das liegt daran, dass sowohl arg1 und arg2 über eine Erweiterungskonvertierung zu Long verfügen, und die Instanzmethode in beiden Fällen Vorrang vor der Erweiterungsmethode hat.

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

Dies bedeutet, dass eine Erweiterungsmethode keine vorhandene Instanzmethode ersetzen kann. Wenn eine Erweiterungsmethode jedoch über denselben Namen wie eine Instanzmethode verfügt, die Signaturen aber keine Konflikte verursachen, kann auf beide Methoden zugegriffen werden. Wenn die ExampleClass beispielsweise eine Methode mit dem Namen ExampleMethod enthält, die keine Argumente verwendet, sind Erweiterungsmethoden mit demselben Namen aber unterschiedlichen Signaturen zulässig, wie in folgendem Code dargestellt.

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

Dieser Code generiert folgende Ausgabe:

Extension method
Instance method

Bezüglich Eigenschaften ist der Sachverhalt unkomplizierter: Wenn eine Erweiterungsmethode den gleichen Namen wie eine Eigenschaft der Klasse hat, die sie erweitert, wird die Erweiterungsmethode nicht angezeigt, und es kann nicht darauf zugegriffen werden.

Rangfolge von Erweiterungsmethoden

Wenn sich zwei Erweiterungsmethoden mit identischen Signaturen im Gültigkeitsbereich befinden und zugreifbar sind, wird die Methode mit der höheren Rangfolge aufgerufen. Die Rangfolge einer Erweiterungsmethode basiert auf dem Mechanismus, der verwendet wird, um die Methode in den Gültigkeitsbereich einzubinden. Die folgende Liste gibt die hierarchische Rangfolge von oben nach unten an:

  1. Im aktuellen Modul definierte Erweiterungsmethoden.

  2. Erweiterungsmethoden, die innerhalb von Datentypen im aktuellen Namespace oder in übergeordneten Namespaces definiert sind, wobei untergeordnete Namespaces eine höhere Rangfolge als übergeordnete Namespaces haben.

  3. In Typimporten in der aktuellen Datei definierte Erweiterungsmethoden.

  4. In Namespaceimporten in der aktuellen Datei definierte Erweiterungsmethoden.

  5. In Typimporten auf Projektebene definierte Erweiterungsmethoden.

  6. In Namespaceimporten auf Projektebene definierte Erweiterungsmethoden.

Wenn sich die Mehrdeutigkeit durch die Anwendung einer Rangfolge nicht auflösen lässt, können Sie den vollqualifizierten Namen zum Festlegen der aufgerufenen Methode verwenden. Wenn die Print-Methode aus dem vorherigen Beispiel in einem Modul mit dem Namen StringExtensions definiert wird, lautet der vollqualifizierte Name StringExtensions.Print(example) und nicht example.Print().

Siehe auch