Rozpoznawanie przeciążonej metody

W praktyce reguły określania rozpoznawania przeciążenia mają na celu znalezienie przeciążenia znajdującego się "najbliżej" podanych argumentów. Jeśli istnieje metoda, której typy parametrów są zgodne z typami argumentów, ta metoda jest oczywiście najbliższa. Oznacza to, że jedna metoda jest bliższa niż inna, jeśli wszystkie jej typy parametrów są węższe niż (lub takie same jak) typy parametrów drugiej metody. Jeśli żadna z parametrów metody nie jest węższa niż druga, nie ma możliwości określenia, która metoda jest bliżej argumentów.

Uwaga. Rozpoznawanie przeciążenia nie uwzględnia oczekiwanego typu zwracanego metody.

Należy również zauważyć, że ze względu na nazwaną składnię parametrów kolejność rzeczywistych i formalnych parametrów może nie być taka sama.

Biorąc pod uwagę grupę metod, najbardziej odpowiednia metoda w grupie dla listy argumentów jest określana przy użyciu poniższych kroków. Jeśli po zastosowaniu określonego kroku żadne elementy członkowskie nie pozostają w zestawie, wystąpi błąd czasu kompilacji. Jeśli tylko jeden element członkowski pozostanie w zestawie, ten element członkowski jest najbardziej odpowiednim elementem członkowskim. Kroki są następujące:

  1. Po pierwsze, jeśli nie podano argumentów typu, zastosuj wnioskowanie typu do dowolnych metod, które mają parametry typu. Jeśli wnioskowanie typu powiedzie się dla metody, argumenty typu wnioskowanego są używane dla tej konkretnej metody. Jeśli wnioskowanie typu nie powiedzie się dla metody, ta metoda zostanie wyeliminowana z zestawu.

  2. Następnie zlikwiduj wszystkie elementy członkowskie z zestawu, które są niedostępne lub nie mają zastosowania (Sekcja Aplikacja do listy argumentów) do listy argumentów

  3. Następnie, jeśli co najmniej jeden argument jest AddressOf lub wyrażenia lambda, oblicz poziomy relaksu delegata dla każdego z takich argumentów, jak poniżej. Jeśli najgorszy (najniższy) poziom relaksu delegata w jest N gorszy niż najniższy poziom relaksu delegata w , Mnależy wyeliminować N z zestawu. Poziomy relaksu delegata są następujące:

    1. Błąd delegowania poziomu relaksu — jeśli AddressOf nie można przekonwertować elementu lub lambda na typ delegata.

    2. Zawężenie delegata złagodzenia zwracanego typu lub parametrów - jeśli argument jest lub lambda z zadeklarowanym typem, a konwersja z zwracanego typu do typu zwracanego delegata jest zawężana; lub jeśli argument jest AddressOf regularną lambdą i konwersją z dowolnego z jego wyrażeń zwracanych do delegata zwraca typ jest zawężony, lub jeśli argument jest asynchroniczny lambda, a typ zwracany delegata jest Task(Of T) i konwersja z dowolnego z jego wyrażeń zwracanych do T jest zawężana; lub jeśli argument jest iteratorem lambda i delegowanym typem IEnumerator(Of T) zwracanym lub IEnumerable(Of T) konwersją z dowolnego z jego operandów wydajności do T jest zawężenie.

    3. Rozszerzanie delegowania relaksu, aby delegować bez podpisu — jeśli typ delegata to System.Delegate lub System.MultiCastDelegate lub System.Object.

    4. Upuść zwracany lub argumenty delegują relaks - jeśli argument jest AddressOf lub lambda z zadeklarowanym typem zwracanym, a typ delegata nie ma zwracanego typu; lub jeśli argument jest lambda z co najmniej jednym wyrażeniem zwracanym, a typ delegata nie ma zwracanego typu; lub jeśli argument jest AddressOf lub lambda bez parametrów, a typ delegata ma parametry.

    5. Rozszerzanie delegata złagodzenia typu zwracanego — jeśli argument jest lub lambda z zadeklarowanym typem zwracanym, i istnieje konwersja rozszerzająca z jego typu zwracanego do typu delegata; lub jeśli argument jest AddressOf zwykłym lambda, gdzie konwersja ze wszystkich wyrażeń zwracanych do delegata typu zwracanego jest rozszerzająca lub tożsamość z co najmniej jednym rozszerzającym; lub jeśli argument jest asynchroniczny lambda, a delegat jest Task(Of T) lub Task i konwersja ze wszystkich wyrażeń zwracanych odpowiednio TObject/do jest rozszerzanie lub tożsamość z co najmniej jednym rozszerzaniem; lub jeśli argument jest iteratorem lambda, a delegat jest IEnumerator(Of T) lub IEnumerable(Of T) lub IEnumerator lub i IEnumerable konwersja ze wszystkich wyrażeń zwracanych do T/Object jest rozszerzanie lub tożsamość z co najmniej jednym rozszerzeniem.

    6. Delegowanie tożsamości — jeśli argument jest argumentem AddressOf lub lambda, który dokładnie pasuje do delegata, bez rozszerzania lub zawężania lub upuszczania parametrów lub zwraca lub zwraca. Następnie, jeśli niektóre elementy członkowskie zestawu nie wymagają zawężania konwersji, aby mieć zastosowanie do żadnego z argumentów, należy wyeliminować wszystkie elementy członkowskie, które to robią. Przykład:

    Sub f(x As Object)
    End Sub
    
    Sub f(x As Short)
    End Sub
    
    Sub f(x As Short())
    End Sub
    
    f("5") ' picks the Object overload, since String->Short is narrowing
    f(5)   ' picks the Object overload, since Integer->Short is narrowing
    f({5}) ' picks the Object overload, since Integer->Short is narrowing
    f({})  ' a tie-breaker rule subsequent to [3] picks the Short() overload
    
    
  4. Następnie eliminacja odbywa się na podstawie zawężenia w następujący sposób. (Należy pamiętać, że jeśli opcja Strict jest włączona, wszystkie elementy członkowskie, które wymagają zawężenia, zostały już ocenione jako stosowane (sekcja Zastosowanie do listy argumentów) i usunięte w kroku 2.

    1. Jeśli niektóre elementy członkowskie zestawu wymagają tylko zawężenia konwersji, w których typ wyrażenia argumentu to Object, należy wyeliminować wszystkie pozostałe elementy członkowskie.
    2. Jeśli zestaw zawiera więcej niż jeden element członkowski, który wymaga zawężenia tylko z Objectelementu , wyrażenie docelowe wywołania zostanie ponownie sklasyfikowane jako dostęp do metody powiązanej z opóźnieniem (i zostanie podany błąd, jeśli typ zawierający grupę metod jest interfejsem lub jeśli którykolwiek z odpowiednich elementów członkowskich były członkami rozszerzenia).
    3. Jeśli istnieją kandydaci, którzy wymagają tylko zawężenia od literałów liczbowych, wybierz najbardziej konkretny spośród wszystkich pozostałych kandydatów, wykonując poniższe kroki. Jeśli zwycięzca wymaga tylko zawężenia z literałów liczbowych, zostanie wybrany w wyniku rozpoznawania przeciążenia; w przeciwnym razie jest to błąd.

    Uwaga. Uzasadnieniem tej reguły jest to, że jeśli program jest luźno typizowane (czyli większość lub wszystkie zmienne są deklarowane jako Object), rozpoznawanie przeciążenia może być trudne, gdy wiele konwersji z Object są zawężone. Zamiast rozpoznawania przeciążenia kończy się niepowodzeniem w wielu sytuacjach (wymaga silnego wpisywania argumentów do wywołania metody), rozwiązanie odpowiedniej przeciążonej metody do wywołania jest odroczone do czasu wykonywania. Dzięki temu luźno wpisane wywołanie zakończy się powodzeniem bez dodatkowych rzutów. Niefortunny efekt uboczny tego jest jednak taki, że wykonanie połączenia z opóźnieniem wymaga odlewania elementu docelowego wywołania do Object. W przypadku wartości struktury oznacza to, że wartość musi być w polu tymczasowym. Jeśli metoda ostatecznie wywołana próbuje zmienić pole struktury, ta zmiana zostanie utracona po powrocie metody. Interfejsy są wykluczone z tej specjalnej reguły, ponieważ opóźnione powiązanie zawsze jest rozpoznawane względem elementów członkowskich klasy środowiska uruchomieniowego lub typu struktury, które mogą mieć inne nazwy niż elementy członkowskie implementowane interfejsy.

  5. Następnie, jeśli jakiekolwiek metody wystąpień pozostają w zestawie, które nie wymagają zawężenia, należy wyeliminować wszystkie metody rozszerzenia z zestawu. Przykład:

    Imports System.Runtime.CompilerServices
    
    Class C3
        Sub M1(d As Integer)
        End Sub
    End Class
    
    Module C3Extensions
        <Extension> _
        Sub M1(c3 As C3, c As Long)
        End Sub
    
        <Extension> _
        Sub M1(c3 As C3, c As Short)
        End Sub
    End Module
    
    Module Test
        Sub Main()
            Dim c As New C3()
            Dim sVal As Short = 10
            Dim lVal As Long = 20
    
            ' Calls C3.M1, since C3.M1 is applicable.
            c.M1(sVal)
    
            ' Calls C3Extensions.M1 since C3.M1 requires a narrowing conversion
            c.M1(lVal)
        End Sub
    End Module
    

    Uwaga. Metody rozszerzenia są ignorowane, jeśli istnieją odpowiednie metody wystąpienia w celu zagwarantowania, że dodanie importu (które może spowodować przełączenie nowych metod rozszerzeń w zakres) nie spowoduje wywołania istniejącej metody wystąpienia w celu ponownego powiązania z metodą rozszerzenia. Biorąc pod uwagę szeroki zakres niektórych metod rozszerzeń (tj. zdefiniowanych na interfejsach i/lub parametrach typu), jest to bezpieczniejsze podejście do wiązania z metodami rozszerzeń.

  6. Następnie, jeśli, biorąc pod uwagę dwa elementy członkowskie zestawu M i N, M jest bardziej szczegółowe (sekcja Specyfika składowych/typów, biorąc pod uwagę listę argumentów) niż N na podstawie listy argumentów, wyeliminuj N z zestawu. Jeśli więcej niż jeden element członkowski pozostaje w zestawie, a pozostałe elementy członkowskie nie są tak samo specyficzne, biorąc pod uwagę listę argumentów, wynik błędu czasu kompilacji.

  7. W przeciwnym razie, biorąc pod uwagę dwa elementy członkowskie zestawu i MN, zastosuj następujące reguły podziału tie w następującej kolejności:

    1. Jeśli M nie ma parametru ParamArray, ale nie, lub jeśli oba, ale MN przekazuje mniej argumentów do parametru ParamArray niż N to robi, należy wyeliminować N z zestawu. Przykład:

      Module Test
          Sub F(a As Object, ParamArray b As Object())
              Console.WriteLine("F(Object, Object())")
          End Sub
      
          Sub F(a As Object, b As Object, ParamArray c As Object())
              Console.WriteLine("F(Object, Object, Object())")
          End Sub
      
         Sub G(Optional a As Object = Nothing)
            Console.WriteLine("G(Object)")
         End Sub
      
         Sub G(ParamArray a As Object())
            Console.WriteLine("G(Object())")
         End Sub    Sub Main()
              F(1)
              F(1, 2)
              F(1, 2, 3)
            G()
          End Sub
      End Module
      

      Powyższy przykład generuje następujące dane wyjściowe:

      F(Object, Object())
      F(Object, Object, Object())
      F(Object, Object, Object())
      G(Object)
      

      Uwaga. Gdy klasa deklaruje metodę z parametrem paramarray, nie jest rzadkością, aby uwzględnić niektóre rozwinięte formularze jako zwykłe metody. Dzięki temu można uniknąć alokacji wystąpienia tablicy, które występuje po wywołaniu rozszerzonej formy metody z parametrem paramarray.

    2. Jeśli M jest zdefiniowany w bardziej pochodnym typie niż N, należy wyeliminować N z zestawu. Przykład:

      Class Base
          Sub F(Of T, U)(x As T, y As U)
          End Sub
      End Class
      
      Class Derived
          Inherits Base
      
          Overloads Sub F(Of T, U)(x As U, y As T)
          End Sub
      End Class
      
      Module Test
          Sub Main()
              Dim d As New Derived()
      
              ' Calls Derived.F
              d.F(10, 10)
          End Sub
      End Module
      

      Ta reguła dotyczy również typów zdefiniowanych przez metody rozszerzenia. Przykład:

      Imports System.Runtime.CompilerServices
      
      Class Base
      End Class
      
      Class Derived
          Inherits Base
      End Class
      
      Module BaseExt
          <Extension> _
          Sub M(b As Base, x As Integer)
          End Sub
      End Module
      
      Module DerivedExt
          <Extension> _
          Sub M(d As Derived, x As Integer)
          End Sub
      End Module
      
      Module Test
          Sub Main()
              Dim b As New Base()
              Dim d As New Derived()
      
              ' Calls BaseExt.M
              b.M(10)
      
              ' Calls DerivedExt.M 
              d.M(10)
          End Sub
      End Module
      
    3. Jeśli M metody i są metodami rozszerzenia, N a typem M docelowym klasy lub struktury jest klasa lub struktura, a typ N docelowy jest interfejsem, wyeliminuj N z zestawu. Przykład:

      Imports System.Runtime.CompilerServices
      
      Interface I1
      End Interface
      
      Class C1
          Implements I1
      End Class
      
      Module Ext1
          <Extension> _
          Sub M(i As I1, x As Integer)
          End Sub
      End Module
      
      Module Ext2
          <Extension> _
          Sub M(c As C1, y As Integer)
          End Sub
      End Module
      
      Module Test
          Sub Main()
              Dim c As New C1()
      
              ' Calls Ext2.M, because Ext1.M is hidden since it extends
              ' an interface.
              c.M(10)
      
              ' Calls Ext1.M
              CType(c, I1).M(10)
          End Sub
      End Module
      
    4. Jeśli M metody N i są metodami rozszerzenia, a typ M docelowy i N są identyczne po podstawieniu parametru typu, a typ M docelowy przed podstawieniem parametru typu nie zawiera parametrów typu, ale typ N docelowy nie, a następnie ma mniej parametrów typu niż typ Ndocelowy , wyeliminować N z zestawu. Przykład:

      Imports System.Runtime.CompilerServices
      
      Module Module1
          Sub Main()
              Dim x As Integer = 1
              x.f(1) ' Calls first "f" extension method
      
              Dim y As New Dictionary(Of Integer, Integer)
              y.g(1) ' Ambiguity error
          End Sub
      
          <Extension()> Sub f(x As Integer, z As Integer)
          End Sub
      
          <Extension()> Sub f(Of T)(x As T, z As T)
          End Sub
      
          <Extension()> Sub g(Of T)(y As Dictionary(Of T, Integer), z As T)
          End Sub
      
          <Extension()> Sub g(Of T)(y As Dictionary(Of T, T), z As T)
          End Sub
      End Module
      
    5. Przed zastąpieniem argumentów typu, jeśli M jest mniej ogólne (Sekcja Ogólna) niż N, należy wyeliminować N z zestawu.

    6. Jeśli M nie jest metodą rozszerzenia i N jest, wyeliminuj N z zestawu.

    7. Jeśli M metody N i są metodami rozszerzeń i M zostały znalezione przed N ( kolekcja metod rozszerzenia sekcji), zlikwiduj N z zestawu. Przykład:

      Imports System.Runtime.CompilerServices
      
      Class C1
      End Class
      
      Namespace N1
          Module N1C1Extensions
              <Extension> _
              Sub M1(c As C1, x As Integer)
              End Sub
          End Module
      End Namespace
      
      Namespace N1.N2
          Module N2C1Extensions
              <Extension> _
              Sub M1(c As C1, y As Integer)
              End Sub
          End Module
      End Namespace
      
      Namespace N1.N2.N3
          Module Test
              Sub Main()
                  Dim x As New C1()
      
                  ' Calls N2C1Extensions.M1
                  x.M1(10)
              End Sub
          End Module
      End Namespace
      

      Jeśli metody rozszerzenia zostały znalezione w tym samym kroku, te metody rozszerzenia są niejednoznaczne. Wywołanie może być zawsze uściślane przy użyciu nazwy modułu standardowego zawierającego metodę rozszerzenia i wywoływania metody rozszerzenia tak, jakby był to zwykły element członkowski. Przykład:

      Imports System.Runtime.CompilerServices
      
      Class C1
      End Class
      
      Module C1ExtA
          <Extension> _
          Sub M(c As C1)
          End Sub
      End Module
      
      Module C1ExtB
          <Extension> _
          Sub M(c As C1)
          End Sub
      End Module
      
      Module Main
          Sub Test()
              Dim c As New C1()
      
              C1.M()               ' Ambiguous between C1ExtA.M and BExtB.M
              C1ExtA.M(c)          ' Calls C1ExtA.M
              C1ExtB.M(c)          ' Calls C1ExtB.M
          End Sub
      End Module
      
    8. Jeśli M i N oba wymagane wnioskowanie typu w celu wygenerowania argumentów typu i M nie wymaga określenia typu dominującego dla dowolnego argumentu typu (tj. każdy argument typu wywnioskowany z jednym typem), ale N nie, wyeliminować N z zestawu.

      Uwaga. Ta reguła gwarantuje, że rozwiązanie przeciążenia, które powiodło się w poprzednich wersjach (gdzie wnioskowanie wielu typów dla argumentu typu spowodowałoby błąd), nadal generuje te same wyniki.

    9. Jeśli rozpoznawanie przeciążeń jest wykonywane w celu rozpoznania celu wyrażenia tworzenia delegata z AddressOf wyrażenia, a zarówno delegata, jak i M funkcji, podczas gdy N jest podroutyną, należy wyeliminować N z zestawu. Podobnie, jeśli zarówno delegat, jak i M są podrouty, podczas gdy N jest funkcją, należy wyeliminować N z zestawu.

    10. Jeśli M nie użyto żadnych opcjonalnych wartości domyślnych parametrów zamiast jawnych argumentów, ale N nie, to wyeliminować N z zestawu.

    11. Przed zastąpieniem argumentów typu, jeśli M ma większą głębokość rodzajowości (Sekcja Ogólna) niż N, należy wyeliminować N z zestawu.

  8. W przeciwnym razie wywołanie jest niejednoznaczne i występuje błąd czasu kompilacji.

Specyfika elementów członkowskich/typów na liście argumentów

Element członkowski M jest traktowany jakoN, biorąc pod uwagę listę Aargumentów , jeśli ich podpisy są takie same lub jeśli każdy typ parametru w elemencie M jest taki sam jak odpowiedni typ parametru w .N

Uwaga. Dwa elementy członkowskie mogą znajdować się w grupie metod z tym samym podpisem ze względu na metody rozszerzenia. Dwa elementy członkowskie mogą być również równie specyficzne, ale nie mają tego samego podpisu ze względu na parametry typu lub rozszerzenie paramarray.

Element członkowski M jest uważany za bardziej szczegółowy niż N w przypadku, gdy ich podpisy są inne, a co najmniej jeden typ parametru w M elemencie jest bardziej szczegółowy niż typ parametru w elemencie NNM. Biorąc pod uwagę parę parametrów Mj i Nj pasujących do argumentu AjMj , typ jest uważany za bardziej szczegółowy niż typNj, jeśli jeden z następujących warunków jest spełniony:

  • Istnieje konwersja rozszerzająca z typu Mj na typ Nj. (Uwaga. Ponieważ typy parametrów są porównywane bez względu na rzeczywisty argument w tym przypadku, konwersja rozszerzająca z wyrażeń stałych na typ liczbowy, do których pasuje wartość, nie jest brana pod uwagę w tym przypadku.

  • Aj to literał 0, Mj jest typem liczbowym i Nj jest typem wyliczanym. (Uwaga. Ta reguła jest niezbędna, ponieważ literał 0 rozszerza się na dowolny typ wyliczony. Ponieważ typ wyliczony rozszerza się do jego typu bazowego, oznacza to, że rozpoznawanie przeciążenia na 0 będzie domyślnie preferować typy wyliczane w typach liczbowych. Otrzymaliśmy wiele opinii, że to zachowanie było sprzeczne.

  • Mji są zarówno typami liczbowymi, jak i NjMj są wcześniejsze niż Nj na liście Byte, , ShortUIntegerLongSByteDecimalUShortDoubleIntegerULongSingle. (Uwaga. Reguła dotycząca typów liczbowych jest przydatna, ponieważ podpisane i niepodpisane typy liczbowe określonego rozmiaru mają tylko konwersje zawężone między nimi. Powyższa reguła dzieli krawat między dwoma typami na rzecz bardziej "naturalnego" typu liczbowego. Jest to szczególnie ważne w przypadku rozpoznawania przeciążenia dla typu, który rozszerza zarówno do podpisanych, jak i niepodpisanych typów liczbowych o określonym rozmiarze, na przykład literału liczbowego, który mieści się w obu tych typach).

  • Mji Nj są typami funkcji delegowanych, a zwracany typ Mj jest bardziej szczegółowy niż zwracany typ AjNj If jest klasyfikowany jako metoda lambda i NjMj jest System.Linq.Expressions.Expression(Of T), a następnie argument typu typu (przy założeniu, że jest to typ delegata) jest zastępowany dla porównywanego typu.

  • Mj jest identyczny z typem Aj, a Nj nie. (Uwaga. Warto zauważyć, że poprzednia reguła różni się nieco od języka C#, ponieważ język C# wymaga, aby typy funkcji delegata miały identyczne listy parametrów przed porównaniem typów zwracanych, podczas gdy język Visual Basic nie.

Rodzajowość

Element członkowski M jest określany jako mniej ogólny niż element członkowski N w następujący sposób:

  1. Jeśli dla każdej pary pasujących parametrów Mj i Nj, Mj jest mniej lub równie ogólne niż Nj w odniesieniu do parametrów typu w metodzie, a co najmniej jeden Mj jest mniej ogólny w odniesieniu do parametrów typu w metodzie.
  2. W przeciwnym razie, jeśli dla każdej pary pasujących parametrów i , jest mniej lub równie ogólne niż Nj w odniesieniu do parametrów typu w typie, a co najmniej jeden Mj jest mniej ogólny w odniesieniu do parametrów typu w typie, jest M mniej ogólny niż N. MjNjMj

Parametr M jest uważany za równie ogólny dla parametru N , jeśli ich typy Mt i Nt oba odnoszą się do parametrów typu lub oba te parametry nie odnoszą się do parametrów typu. M jest uważany za mniej ogólny niż N jeśli Mt nie odwołuje się do parametru typu i Nt nie.

Przykład:

Class C1(Of T)
    Sub S1(Of U)(x As U, y As T)
    End Sub

    Sub S1(Of U)(x As U, y As U)
    End Sub

    Sub S2(x As Integer, y As T)
    End Sub

    Sub S2(x As T, y As T)
    End Sub
End Class

Module Test
    Sub Main()
        Dim x As C1(Of Integer) = New C1(Of Integer)

        x.S1(10, 10)    ' Calls S1(U, T)
        x.S2(10, 10)    ' Calls S2(Integer, T)
    End Sub
End Module

Parametry typu metody rozszerzenia, które zostały naprawione podczas curryingu, są traktowane jako parametry typu w typie, a nie parametry typu w metodzie. Przykład:

Imports System.Runtime.CompilerServices

Module Ext1
    <Extension> _
    Sub M1(Of T, U)(x As T, y As U, z As U)
    End Sub
End Module

Module Ext2
    <Extension> _
    Sub M1(Of T, U)(x As T, y As U, z As T)
    End Sub
End Module

Module Test
    Sub Main()
        Dim i As Integer = 10

        i.M1(10, 10)
    End Sub
End Module

Głębokość ogólnej

Element członkowski M jest określany jako o większej głębokości ogólnej niż element członkowski N , jeśli dla każdej pary pasujących parametrów Mj i Nj, Mj ma większą lub równą głębokość rodzajowości niż Nj, a co najmniej jeden Mj ma większą głębokość rodzajowości. Głębokość rodzajowości jest definiowana w następujący sposób:

  • Wszystkie inne niż parametr typu mają większą głębokość rodzajowości niż parametr typu;

  • Rekursywnie skonstruowany typ ma większą głębokość rodzajowości niż inny skonstruowany typ (z taką samą liczbą argumentów typu), jeśli co najmniej jeden argument typu ma większą głębokość rodzajowości, a żaden argument typu nie ma mniejszej głębokości niż odpowiedni argument typu w drugiej.

  • Typ tablicy ma większą głębokość rodzajowości niż inny typ tablicy (o tej samej liczbie wymiarów), jeśli typ elementu pierwszego ma większą głębokość rodzajowości niż typ elementu drugiego.

Przykład:

Module Test

    Sub f(Of T)(x As Task(Of T))
    End Sub

    Sub f(Of T)(x As T)
    End Sub

    Sub Main()
        Dim x As Task(Of Integer) = Nothing
        f(x)            ' Calls the first overload
    End Sub
End Module

Zastosowanie do listy argumentów

Metoda ma zastosowanie do zestawu argumentów typu, argumentów pozycyjnych i nazwanych argumentów, jeśli można wywołać metodę przy użyciu list argumentów. Listy argumentów są dopasowywane do list parametrów w następujący sposób:

  1. Najpierw dopasuj każdy argument pozycyjny do listy parametrów metody. Jeśli istnieje więcej argumentów pozycyjnych niż parametry, a ostatni parametr nie jest paramarray, metoda nie ma zastosowania. W przeciwnym razie parametr paramarray jest rozszerzany o parametry typu elementu paramarray, aby dopasować liczbę argumentów pozycyjnych. Jeśli argument pozycyjny zostanie pominięty, który przejdzie do paramarray, metoda nie ma zastosowania.
  2. Następnie dopasuj każdy nazwany argument do parametru o podanej nazwie. Jeśli jeden z nazwanych argumentów nie jest zgodny, pasuje do parametru paramarray lub pasuje do argumentu już dopasowanego z innym argumentem pozycyjnym lub nazwanym, metoda nie ma zastosowania.
  3. Następnie, jeśli określono argumenty typu, są one dopasowywane do listy parametrów typu . Jeśli dwie listy nie mają tej samej liczby elementów, metoda nie ma zastosowania, chyba że lista argumentów typu jest pusta. Jeśli lista argumentów typu jest pusta, wnioskowanie typu służy do próby wywnioskowania listy argumentów typu. Jeśli wnioskowanie typu nie powiedzie się, metoda nie ma zastosowania. W przeciwnym razie argumenty typu są wypełniane zamiast parametrów typu w podpisie. Jeśli parametry, które nie zostały dopasowane, nie są opcjonalne, metoda nie ma zastosowania.
  4. Jeśli wyrażenia argumentów nie są niejawnie konwertowane na typy dopasowanych parametrów, metoda nie ma zastosowania.
  5. Jeśli parametr ma wartość ByRef i nie ma niejawnej konwersji z typu parametru do typu argumentu, metoda nie ma zastosowania.
  6. Jeśli argumenty typu naruszają ograniczenia metody (w tym argumenty typu wnioskowanego z kroku 3), metoda nie ma zastosowania. Przykład:
Module Module1
    Sub Main()
        f(Of Integer)(New Exception)
        ' picks the first overload (narrowing),
        ' since the second overload (widening) violates constraints 
    End Sub

    Sub f(Of T)(x As IComparable)
    End Sub

    Sub f(Of T As Class)(x As Object)
    End Sub
End Module

Jeśli pojedyncze wyrażenie argumentu pasuje do parametru paramarray, a typ wyrażenia argumentu jest konwertowany zarówno na typ parametru paramarray, jak i typ elementu paramarray, metoda ma zastosowanie zarówno w postaci rozszerzonej, jak i niewyświetliwej, z dwoma wyjątkami. Jeśli konwersja z typu wyrażenia argumentu na typ paramarray jest zawężana, metoda ma zastosowanie tylko w rozszerzonym formularzu. Jeśli wyrażenie argumentu jest literałem Nothing, metoda ma zastosowanie tylko w postaci nierozciągniętych. Przykład:

Module Test
    Sub F(ParamArray a As Object())
        Dim o As Object

        For Each o In a
            Console.Write(o.GetType().FullName)
            Console.Write(" ")
        Next o
        Console.WriteLine()
    End Sub

    Sub Main()
        Dim a As Object() = { 1, "Hello", 123.456 }
        Dim o As Object = a

        F(a)
        F(CType(a, Object))
        F(o)
        F(CType(o, Object()))
    End Sub
End Module

Powyższy przykład generuje następujące dane wyjściowe:

System.Int32 System.String System.Double
System.Object[]
System.Object[]
System.Int32 System.String System.Double

W pierwszych i ostatnich wywołaniach Fklasy , ma zastosowanie normalna forma F , ponieważ konwersja rozszerzająca istnieje z typu argumentu do typu parametru (oba są typu Object()), a argument jest przekazywany jako zwykły parametr wartości. W drugim i trzecim wywołaniu normalna forma F nie ma zastosowania, ponieważ nie istnieje konwersja rozszerzająca z typu argumentu na typ parametru (konwersje z Object do Object() są zawężające). Jednak rozszerzona forma F elementu ma zastosowanie, a jeden element Object() jest tworzony przez wywołanie. Pojedynczy element tablicy jest inicjowany przy użyciu podanej wartości argumentu (która sama jest odwołaniem do obiektu Object()).

Przekazywanie argumentów i wybieranie argumentów dla parametrów opcjonalnych

Jeśli parametr jest parametrem wartości, zgodne wyrażenie argumentu musi być klasyfikowane jako wartość. Wartość jest konwertowana na typ parametru i przekazywana jako parametr w czasie wykonywania. Jeśli parametr jest parametrem referencyjnym, a zgodne wyrażenie argumentu jest klasyfikowane jako zmienna, której typ jest taki sam jak parametr, odwołanie do zmiennej jest przekazywane jako parametr w czasie wykonywania.

W przeciwnym razie, jeśli zgodne wyrażenie argumentu jest klasyfikowane jako zmienna, wartość lub dostęp do właściwości, zostanie przydzielona tymczasowa zmienna typu parametru. Przed wywołaniem metody w czasie wykonywania wyrażenie argumentu zostanie ponownie sklasyfikowane jako wartość, przekonwertowane na typ parametru i przypisane do zmiennej tymczasowej. Następnie jako parametr jest przekazywane odwołanie do zmiennej tymczasowej. Po obliczeniu wywołania metody, jeśli wyrażenie argumentu jest klasyfikowane jako zmienna lub dostęp do właściwości, zmienna tymczasowa jest przypisywana do wyrażenia zmiennej lub wyrażenia dostępu do właściwości. Jeśli wyrażenie dostępu do właściwości nie Set ma metody dostępu, przypisanie nie jest wykonywane.

W przypadku parametrów opcjonalnych, w których nie podano argumentu, kompilator wybiera argumenty zgodnie z poniższym opisem. We wszystkich przypadkach testuje on typ parametru po podstawieniu typu ogólnego.

  • Jeśli opcjonalny parametr ma atrybut System.Runtime.CompilerServices.CallerLineNumber, a wywołanie pochodzi z lokalizacji w kodzie źródłowym, a literał liczbowy reprezentujący numer wiersza tej lokalizacji ma wewnętrzną konwersję na typ parametru, jest używany literał liczbowy. Jeśli wywołanie obejmuje wiele wierszy, wybór wiersza do użycia jest zależny od implementacji.

  • Jeśli opcjonalny parametr ma atrybut System.Runtime.CompilerServices.CallerFilePath, a wywołanie pochodzi z lokalizacji w kodzie źródłowym, a literał ciągu reprezentujący ścieżkę pliku tej lokalizacji ma wewnętrzną konwersję na typ parametru, jest używany literał ciągu. Format ścieżki pliku jest zależny od implementacji.

  • Jeśli opcjonalny parametr ma atrybut System.Runtime.CompilerServices.CallerMemberName, a wywołanie znajduje się w treści elementu członkowskiego typu lub w atrybucie zastosowanym do dowolnej części elementu członkowskiego tego typu, a literał ciągu reprezentujący tę nazwę elementu członkowskiego ma wewnętrzną konwersję na typ parametru, jest używany literał ciągu. W przypadku wywołań, które są częścią metod dostępu właściwości lub niestandardowych procedur obsługi zdarzeń, używana nazwa elementu członkowskiego jest właściwością lub zdarzeniem. W przypadku wywołań, które są częścią operatora lub konstruktora, używana jest nazwa specyficzna dla implementacji.

Jeśli żadna z powyższych wartości nie ma zastosowania, zostanie użyta wartość domyślna opcjonalnego parametru (lub Nothing jeśli nie zostanie podana żadna wartość domyślna). A jeśli ma zastosowanie więcej niż jeden z powyższych elementów, wybór, którego należy użyć, jest zależny od implementacji.

Atrybuty CallerLineNumber i CallerFilePath są przydatne do rejestrowania. Jest CallerMemberName to przydatne do implementowania INotifyPropertyChangedelementu . Oto przykłady.

Sub Log(msg As String,
        <CallerFilePath> Optional file As String = Nothing,
        <CallerLineNumber> Optional line As Integer? = Nothing)
    Console.WriteLine("{0}:{1} - {2}", file, line, msg)
End Sub

WriteOnly Property p As Integer
    Set(value As Integer)
        Notify(_p, value)
    End Set
End Property

Private _p As Integer

Sub Notify(Of T As IEquatable(Of T))(ByRef v1 As T, v2 As T,
        <CallerMemberName> Optional prop As String = Nothing)
    If v1 IsNot Nothing AndAlso v1.Equals(v2) Then Return
    If v1 Is Nothing AndAlso v2 Is Nothing Then Return
    v1 = v2
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
End Sub

Oprócz powyższych parametrów opcjonalnych program Microsoft Visual Basic rozpoznaje również dodatkowe parametry opcjonalne, jeśli są importowane z metadanych (tj. z odwołania do biblioteki DLL):

  • Podczas importowania z metadanych język Visual Basic traktuje również parametr jako wskazujący, że parametr jest opcjonalny: w ten sposób można zaimportować deklarację, która ma opcjonalny parametr <Optional> , ale nie ma wartości domyślnej, mimo że nie można go wyrazić przy użyciu słowa kluczowego Optional .

  • Jeśli opcjonalny parametr ma atrybut Microsoft.VisualBasic.CompilerServices.OptionCompareAttribute, a literał liczbowy 1 lub 0 ma konwersję na typ parametru, kompilator używa jako argumentu literału 1, jeśli jest w mocy, lub literału 0, jeśli Option Compare TextOptional Compare Binary jest w mocy.

  • Jeśli opcjonalny parametr ma atrybut System.Runtime.CompilerServices.IDispatchConstantAttribute, i ma typ Object, i nie określa wartości domyślnej, kompilator używa argumentu New System.Runtime.InteropServices.DispatchWrapper(Nothing).

  • Jeśli opcjonalny parametr ma atrybut System.Runtime.CompilerServices.IUnknownConstantAttribute, i ma typ Object, i nie określa wartości domyślnej, kompilator używa argumentu New System.Runtime.InteropServices.UnknownWrapper(Nothing).

  • Jeśli opcjonalny parametr ma typ Object, i nie określa wartości domyślnej, kompilator używa argumentu System.Reflection.Missing.Value.

Metody warunkowe

Jeśli metoda docelowa, do której odwołuje się wyrażenie wywołania, jest podroutyną, która nie jest elementem członkowskim interfejsu, a jeśli metoda ma co najmniej jeden System.Diagnostics.ConditionalAttribute atrybut, ocena wyrażenia zależy od stałych kompilacji warunkowej zdefiniowanych w tym momencie w pliku źródłowym. Każde wystąpienie atrybutu określa ciąg, który nazywa stałą kompilacji warunkowej. Każda stała kompilacji warunkowej jest oceniana tak, jakby była częścią instrukcji kompilacji warunkowej. Jeśli stała daje wartość , wyrażenie jest obliczane Truenormalnie w czasie wykonywania. Jeśli stała daje wartość , Falsewyrażenie nie jest w ogóle oceniane.

Podczas wyszukiwania atrybutu sprawdzana jest najbardziej pochodna deklaracja metody zastępowalnej.

Uwaga. Atrybut nie jest prawidłowy w funkcjach lub metodach interfejsu i jest ignorowany, jeśli określono dla dowolnego rodzaju metody. W związku z tym metody warunkowe będą wyświetlane tylko w instrukcjach wywołania.

Wnioskowanie argumentu typu

Gdy metoda z parametrami typu jest wywoływana bez określania argumentów typu, wnioskowanie argumentów typu służy do próby wnioskowania argumentów typu dla wywołania. Dzięki temu można używać bardziej naturalnej składni do wywoływania metody z parametrami typu, gdy argumenty typu mogą być wnioskowane trywialnie. Na przykład, biorąc pod uwagę następującą deklarację metody:

Module Util
    Function Choose(Of T)(b As Boolean, first As T, second As T) As T
        If b Then
            Return first
        Else
            Return second
        End If
    End Function
End Class

Można wywołać metodę Choose bez jawnego określenia argumentu typu:

' calls Choose(Of Integer)
Dim i As Integer = Util.Choose(True, 5, 213)
' calls Choose(Of String)
Dim s As String = Util.Choose(False, "a", "b") 

Za pośrednictwem wnioskowania argumentów typu argumenty Integer typu i String są określane z argumentów do metody.

Wnioskowanie argumentu typu występuje przed wykonaniem ponownej klasyfikacji wyrażenia na metodach lambda lub wskaźnikach metod na liście argumentów, ponieważ ponowne sklasyfikowanie tych dwóch rodzajów wyrażeń może wymagać, aby typ parametru był znany. Biorąc pod uwagę zestaw argumentów A1,...,An, zestaw pasujących parametrów i zestaw parametrów P1,...,PnT1,...,Tntypu metody , zależności między argumentami i parametrami typu metody są najpierw zbierane w następujący sposób:

  • Jeśli An jest literałem Nothing , nie są generowane żadne zależności.

  • Jeśli An jest metodą lambda, a typem Pn obiektu jest skonstruowany typ delegata lub System.Linq.Expressions.Expression(Of T), gdzie T jest skonstruowanym typem delegata,

  • Jeśli typ parametru metody lambda zostanie wywnioskowany z typu odpowiedniego parametru , a typ parametru Pnzależy od parametru Tntypu metody , An ma zależność od Tn.

  • Jeśli typ parametru metody lambda jest określony, a typ odpowiedniego parametru Pn zależy od parametru Tntypu metody , Tn ma zależność od An.

  • Jeśli zwracany typ metody zależy od parametru PnTntypu metody , Tn ma zależność od An.

  • Jeśli An jest wskaźnikiem metody, a typem Pn obiektu jest skonstruowany typ delegata,

  • Jeśli zwracany typ metody zależy od parametru PnTntypu metody , Tn ma zależność od An.

  • Jeśli Pn jest typem skonstruowanym, a typ właściwości zależy od parametru PnTntypu metody , Tn ma zależność od Anklasy .

  • W przeciwnym razie nie jest generowana żadna zależność.

Po zebraniu zależności wszystkie argumenty, które nie mają żadnych zależności, zostaną wyeliminowane. Jeśli jakiekolwiek parametry typu metody nie mają zależności wychodzących (tj. parametr typu metody nie zależy od argumentu), wnioskowanie typu kończy się niepowodzeniem. W przeciwnym razie pozostałe argumenty i parametry typu metody są pogrupowane w silnie połączone składniki. Silnie połączony składnik to zestaw argumentów i parametrów typu metody, gdzie dowolny element w składniku jest osiągalny za pośrednictwem zależności od innych elementów.

Silnie połączone składniki są następnie sortowane topologicznie i przetwarzane w kolejności topologicznej:

  • Jeśli silnie typowany składnik zawiera tylko jeden element,

    • Jeśli element został już oznaczony jako ukończony, pomiń go.

    • Jeśli element jest argumentem, dodaj wskazówki dotyczące typu z argumentu do parametrów typu metody, które od niego zależą, i oznacz element jako ukończony. Jeśli argument jest metodą lambda z parametrami, które nadal wymagają wywnioskowanych typów, wywnioskuj Object typy tych parametrów.

    • Jeśli element jest parametrem typu metody, wywnioskuj parametr typu metody jako dominujący typ wśród wskazówek typu argumentu i oznacz element jako ukończony. Jeśli wskazówka typu ma ograniczenie elementu tablicy, uwzględniane są tylko konwersje prawidłowe między tablicami danego typu (tj. kowariantne i wewnętrzne konwersje tablic). Jeśli wskazówka typu ma ogólne ograniczenie argumentu, uwzględniane są tylko konwersje tożsamości. Jeśli nie można wybrać typu dominującego, wnioskowanie kończy się niepowodzeniem. Jeśli jakiekolwiek typy argumentów metody lambda zależą od tego parametru typu metody, typ jest propagowany do metody lambda.

  • Jeśli silnie typowany składnik zawiera więcej niż jeden element, składnik zawiera cykl.

    • Dla każdego parametru typu metody, który jest elementem w składniku, jeśli parametr typu metody zależy od argumentu, który nie jest oznaczony jako ukończony, przekonwertuj tę zależność na asercję, która zostanie sprawdzona na końcu procesu wnioskowania.

    • Uruchom ponownie proces wnioskowania w momencie, w którym określono silnie typizowane składniki.

Jeśli wnioskowanie typu powiedzie się dla wszystkich parametrów typu metody, sprawdzane są wszystkie zależności, które zostały zmienione na asercje. Asercji powiedzie się, jeśli typ argumentu jest niejawnie konwertowany na wywnioskowany typ parametru typu metody. Jeśli asercja nie powiedzie się, wnioskowanie argumentu zakończy się niepowodzeniem.

Biorąc pod uwagę typ Ta argumentu dla argumentu A i typ Tp parametru dla parametru P, wskazówki dotyczące typu są generowane w następujący sposób:

  • Jeśli Tp nie obejmuje żadnych parametrów typu metody, nie są generowane żadne wskazówki.

  • Jeśli Tp i Ta są typami tablic tej samej rangi, zastąp Ta wartości i Tp typami Ta elementów i Tp uruchom ponownie ten proces za pomocą ograniczenia elementu tablicy.

  • Jeśli Tp jest parametrem typu metody, Ta zostanie dodany jako wskazówka typu z bieżącym ograniczeniem, jeśli istnieje.

  • Jeśli A jest metodą lambda i Tp jest skonstruowanym typem delegata lub System.Linq.Expressions.Expression(Of T), gdzie T jest skonstruowanym typem delegata, dla każdego typu parametru TL metody lambda i odpowiadającego mu typu TDparametru delegata, zastąp TDTaTL ciąg i i Tp uruchom ponownie proces bez ograniczeń. Następnie zastąp Ta ciąg zwracanym typem metody lambda i:

    • jeśli A jest zwykłą metodą lambda, zastąp Tp zwracanym typem typu delegata;
    • jeśli A jest metodą lambda asynchronicznej, a zwracany typ typu delegata ma postać Task(Of T) dla niektórych T, zastąp Tp element tym ;T
    • jeśli A jest metodą lambda iteratora, a zwracany typ typu delegata ma formę IEnumerator(Of T) lub IEnumerable(Of T) dla niektórych T, zastąp Tp element tym T.
    • Następnie uruchom ponownie proces bez ograniczeń.
  • Jeśli A jest wskaźnikiem metody i Tp jest skonstruowanym typem delegata, użyj typów parametrów , Tp aby określić, która metoda ma największe zastosowanie do Tp. Jeśli istnieje metoda, która jest najbardziej odpowiednia, zastąp Ta ciąg zwracanym typem metody i Tp typem zwracanym typu delegata i uruchom ponownie proces bez ograniczeń.

  • Tp W przeciwnym razie musi być typem skonstruowanym. Biorąc pod uwagę TGtyp ogólny , Tp,

    • Jeśli Ta element ma TGwartość , dziedziczy z TG, lub implementuje typ TG dokładnie raz, a następnie dla każdego zgodnego argumentu Tax typu z Ta i TpxTpz , zastąp Ta element i TaxTpTpx ponownie uruchom proces z ogólnym ograniczeniem argumentu.

    • W przeciwnym razie wnioskowanie typu kończy się niepowodzeniem dla metody ogólnej.

Powodzenie wnioskowania typu nie gwarantuje, że metoda ma zastosowanie.