Metody rozszerzeń (Visual Basic)

Metody rozszerzeń umożliwiają deweloperom dodawanie niestandardowych funkcji do typów danych, które są już zdefiniowane bez tworzenia nowego typu pochodnego. Metody rozszerzenia umożliwiają napisanie metody, która może być wywoływana tak, jakby była to metoda wystąpienia istniejącego typu.

Uwagi

Metoda rozszerzenia może być tylko procedurą Sub lub procedurą Function . Nie można zdefiniować właściwości rozszerzenia, pola lub zdarzenia. Wszystkie metody rozszerzenia muszą być oznaczone atrybutem <Extension> rozszerzenia z System.Runtime.CompilerServices przestrzeni nazw i muszą być zdefiniowane w module. Jeśli metoda rozszerzenia jest zdefiniowana poza modułem, kompilator języka Visual Basic generuje błąd BC36551" "Metody rozszerzeń można zdefiniować tylko w modułach".

Pierwszy parametr w definicji metody rozszerzenia określa, który typ danych rozszerza metoda. Po uruchomieniu metody pierwszy parametr jest powiązany z wystąpieniem typu danych, który wywołuje metodę.

Atrybut Extension można zastosować tylko do języka Visual Basic Module, Sublub Function. Jeśli zastosujesz go do elementu Class lub Structure, kompilator języka Visual Basic generuje błąd BC36550, atrybut "Rozszerzenie" można zastosować tylko do deklaracji "Module", "Sub" lub "Function".

Przykład

W poniższym przykładzie zdefiniowano Print rozszerzenie String typu danych. Metoda używa Console.WriteLine metody do wyświetlania ciągu. Parametr Print metody określa, aStringże metoda rozszerza klasę String .

Imports System.Runtime.CompilerServices

Module StringExtensions

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

End Module

Zwróć uwagę, że definicja metody rozszerzenia jest oznaczona atrybutem <Extension()>rozszerzenia . Oznaczanie modułu, w którym zdefiniowano metodę, jest opcjonalne, ale każda metoda rozszerzenia musi być oznaczona. System.Runtime.CompilerServices Aby uzyskać dostęp do atrybutu rozszerzenia, należy go zaimportować.

Metody rozszerzeń można zadeklarować tylko w ramach modułów. Zazwyczaj moduł, w którym zdefiniowano metodę rozszerzenia, nie jest tym samym modułem, w którym jest wywoływany. Zamiast tego importowany jest moduł zawierający metodę rozszerzenia, jeśli jest to konieczne, aby przenieść go do zakresu. Po module, który zawiera Print , znajduje się w zakresie, metoda może być wywoływana tak, jakby była to zwykła metoda wystąpienia, która nie przyjmuje żadnych argumentów, takich jak 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

W następnym przykładzie PrintAndPunctuateparametr , jest również rozszerzeniem do String, tym razem zdefiniowanym z dwoma parametrami. Pierwszy parametr określa, aStringże metoda rozszerzenia rozszerza Stringwartość . Drugi parametr , puncma być ciągiem znaków interpunkcyjnych przekazywanych jako argument, gdy metoda jest wywoływana. Metoda wyświetla ciąg, po którym następuje znaki interpunkcyjne.

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

Metoda jest wywoływana przez wysłanie w argumencie ciągu dla puncelementu : example.PrintAndPunctuate(".")

W poniższym przykładzie pokazano Print i zdefiniowano i PrintAndPunctuate wywołaliśmy polecenie . System.Runtime.CompilerServices Jest importowany w module definicji w celu umożliwienia dostępu do atrybutu rozszerzenia.

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

Następnie metody rozszerzenia są wprowadzane do zakresu i wywoływane:

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

Wszystko, co jest wymagane, aby móc uruchomić te lub podobne metody rozszerzenia, jest to, że są one w zakresie. Jeśli moduł zawierający metodę rozszerzenia jest w zakresie, jest widoczny w funkcji IntelliSense i może być wywoływany tak, jakby był to zwykła metoda wystąpienia.

Zwróć uwagę, że po wywołaniu metod żaden argument nie jest wysyłany dla pierwszego parametru. Parametr aString w poprzednich definicjach metody jest powiązany z examplewystąpieniem String metody , które je wywołuje. Kompilator będzie używać example jako argumentu wysyłanego do pierwszego parametru.

Jeśli metoda rozszerzenia jest wywoływana dla obiektu, który jest ustawiony na Nothing, metoda rozszerzenia jest wykonywana. Nie dotyczy to zwykłych metod wystąpień. Możesz jawnie sprawdzić Nothing w metodzie rozszerzenia.

Typy, które można rozszerzyć

Można zdefiniować metodę rozszerzenia dla większości typów, które mogą być reprezentowane na liście parametrów języka Visual Basic, w tym następujące:

  • Klasy (typy referencyjne)
  • Struktury (typy wartości)
  • Interfejsy
  • Delegaci
  • Argumenty ByRef i ByVal
  • Parametry metody ogólnej
  • Tablice

Ponieważ pierwszy parametr określa typ danych, który rozszerza metoda rozszerzenia, jest wymagany i nie może być opcjonalny. Z tego powodu Optional parametry i ParamArray parametry nie mogą być pierwszym parametrem na liście parametrów.

Metody rozszerzeń nie są uznawane za opóźnione powiązanie. W poniższym przykładzie instrukcja anObject.PrintMe() zgłasza MissingMemberException wyjątek, ten sam wyjątek, który można zobaczyć, czy druga PrintMe definicja metody rozszerzenia została usunięta.

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

Najlepsze rozwiązania

Metody rozszerzeń zapewniają wygodny i zaawansowany sposób rozszerzania istniejącego typu. Jednak aby pomyślnie ich używać, należy wziąć pod uwagę pewne kwestie. Te zagadnienia dotyczą głównie autorów bibliotek klas, ale mogą mieć wpływ na dowolną aplikację korzystającą z metod rozszerzeń.

Na ogół metody rozszerzeń dodawane do typów, których nie jesteś właścicielem, są bardziej podatne na zagrożenia niż metody rozszerzeń dodawane do typów, które kontrolujesz. Wiele rzeczy może wystąpić w klasach, które nie są właścicielami, które mogą zakłócać metody rozszerzeń.

  • Jeśli istnieje dowolny dostępny element członkowski wystąpienia, który ma podpis zgodny z argumentami w instrukcji wywołującej, bez konwersji zawężających z argumentu do parametru, metoda wystąpienia będzie używana w preferencji do dowolnej metody rozszerzenia. W związku z tym jeśli w pewnym momencie do klasy zostanie dodana odpowiednia metoda wystąpienia, istniejący element członkowski rozszerzenia, którego używasz, może stać się niedostępny.

  • Autor metody rozszerzenia nie może uniemożliwić innym programistom pisania sprzecznych metod rozszerzenia, które mogą mieć pierwszeństwo przed oryginalnym rozszerzeniem.

  • Niezawodność można poprawić, umieszczając metody rozszerzeń we własnej przestrzeni nazw. Użytkownicy biblioteki mogą następnie dołączyć przestrzeń nazw lub wykluczyć ją albo wybrać przestrzeń nazw oddzielnie od pozostałej części biblioteki.

  • Rozszerzenie interfejsów może być bezpieczniejsze niż rozszerzenie klas, zwłaszcza jeśli nie jesteś właścicielem interfejsu lub klasy. Zmiana interfejsu ma wpływ na każdą klasę, która ją implementuje. W związku z tym autor może być mniej prawdopodobne, aby dodać lub zmienić metody w interfejsie. Jeśli jednak klasa implementuje dwa interfejsy, które mają metody rozszerzenia z tym samym podpisem, żadna z metod rozszerzenia nie jest widoczna.

  • Rozszerz najbardziej konkretny typ, który możesz. W hierarchii typów, jeśli wybierzesz typ, z którego pochodzą wiele innych typów, istnieją warstwy możliwości wprowadzenia metod wystąpienia lub innych metod rozszerzeń, które mogą zakłócać działanie.

Metody rozszerzeń, metody wystąpień i właściwości

Gdy metoda wystąpienia w zakresie ma podpis zgodny z argumentami instrukcji wywołującej, metoda wystąpienia jest wybierana w preferencjach dla dowolnej metody rozszerzenia. Metoda wystąpienia ma pierwszeństwo, nawet jeśli metoda rozszerzenia jest lepiej zgodna. W poniższym przykładzie zawiera metodę wystąpienia o nazwie ExampleMethod , ExampleClass która ma jeden parametr typu Integer. Metoda rozszerzenia rozszerza metodę ExampleMethodExampleClassi ma jeden parametr typu 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

Pierwsze wywołanie metody ExampleMethod w poniższym kodzie wywołuje metodę rozszerzenia, ponieważ arg1 jest Long zgodne tylko z parametrem Long w metodzie rozszerzenia. Drugie wywołanie ExampleMethod metody ma Integer argument arg2, i wywołuje metodę wystąpienia.

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

Teraz odwrócić typy danych parametrów w dwóch metodach:

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

Tym razem kod w Main metodzie wywołuje metodę wystąpienia w obu przypadkach. Dzieje się tak dlatego, że zarówno arg1 , jak i arg2 mają konwersję rozszerzającą na Long, a metoda wystąpienia ma pierwszeństwo przed metodą rozszerzenia w obu przypadkach.

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

W związku z tym metoda rozszerzenia nie może zastąpić istniejącej metody wystąpienia. Jeśli jednak metoda rozszerzenia ma taką samą nazwę jak metoda wystąpienia, ale sygnatury nie powodują konfliktu, można uzyskać dostęp do obu metod. Jeśli na przykład klasa ExampleClass zawiera metodę o nazwie ExampleMethod , która nie przyjmuje żadnych argumentów, metody rozszerzenia o tej samej nazwie, ale różne podpisy są dozwolone, jak pokazano w poniższym kodzie.

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

Dane wyjściowe z tego kodu są następujące:

Extension method
Instance method

Sytuacja jest prostsza z właściwościami: jeśli metoda rozszerzenia ma taką samą nazwę jak właściwość klasy, która rozszerza, metoda rozszerzenia nie jest widoczna i nie można uzyskać do niej dostępu.

Pierwszeństwo metody rozszerzenia

Gdy dwie metody rozszerzenia, które mają identyczne podpisy, znajdują się w zakresie i są dostępne, zostanie wywołana jedna z wyższym pierwszeństwem. Pierwszeństwo metody rozszerzenia opiera się na mechanizmie używanym do przeniesienia metody do zakresu. Na poniższej liście przedstawiono hierarchię pierwszeństwa od najwyższego do najniższego.

  1. Metody rozszerzeń zdefiniowane wewnątrz bieżącego modułu.

  2. Metody rozszerzeń zdefiniowane wewnątrz typów danych w bieżącej przestrzeni nazw lub dowolnej z jej elementów nadrzędnych, z podrzędnymi przestrzeniami nazw o wyższym prioryencie niż nadrzędne przestrzenie nazw.

  3. Metody rozszerzenia zdefiniowane wewnątrz dowolnego typu importu w bieżącym pliku.

  4. Metody rozszerzeń zdefiniowane wewnątrz dowolnego importu przestrzeni nazw w bieżącym pliku.

  5. Metody rozszerzeń zdefiniowane wewnątrz dowolnego importu typu na poziomie projektu.

  6. Metody rozszerzeń zdefiniowane wewnątrz dowolnego importu przestrzeni nazw na poziomie projektu.

Jeśli pierwszeństwo nie rozwiąże niejednoznaczności, możesz użyć w pełni kwalifikowanej nazwy, aby określić wywoływaną metodę. Print Jeśli metoda we wcześniejszym przykładzie jest zdefiniowana w module o nazwie StringExtensions, w pełni kwalifikowana nazwa to StringExtensions.Print(example) zamiast example.Print().

Zobacz też