通过


重载方法解析

在实践中,确定重载解析的规则旨在查找“最接近”提供的实际参数的重载。 如果某个方法的参数类型与参数类型匹配,则该方法显然是最接近的方法。 否则,如果其所有参数类型都比另一种方法的参数类型窄(或相同),则一个方法比另一个方法的参数类型更近。 如果两个方法的参数都不比另一种方法窄,则无法确定哪个方法更接近参数。

注意。 重载解析不考虑方法的预期返回类型。

另请注意,由于命名参数语法,实际参数和正式参数的顺序可能不同。

给定方法组后,参数列表组中最适用的方法是使用以下步骤确定的。 如果在应用特定步骤后,没有成员保留在集中,则会发生编译时错误。 如果集中只有一个成员,则该成员是最适用的成员。 这些步骤包括:

  1. 首先,如果没有提供类型参数,请对具有类型参数的任何方法应用类型推理。 如果方法的类型推理成功,则推断的类型参数将用于该特定方法。 如果方法的类型推理失败,则会从集中消除该方法。

  2. 接下来,从不可访问或不适用的集中消除所有成员(“ 参数列表适用性”)到参数列表

  3. 接下来,如果一个或多个参数是 AddressOf 或 lambda 表达式,则计算每个此类参数的 委托放宽级别 ,如下所示。 如果最差(最低)委托放松级别比其中M的最低委托放松级别N差,则从集中消除N。 委托放宽级别如下所示:

    1. 错误委托放宽级别 - 如果 AddressOf 或 lambda 无法转换为委托类型。

    2. 缩小返回类型或参数的委托放宽 - 如果参数是 AddressOf 或具有声明类型的 lambda,并且从其返回类型到委托返回类型的转换正在缩小;或者如果参数是正则 lambda,并且从任何返回表达式到委托返回类型的转换正在缩小, 或者,如果参数是异步 lambda 并且委托返回类型是 Task(Of T) ,并且从其任何返回表达式到 T 的转换正在缩小;或者参数是迭代器 lambda 和委托返回类型 IEnumerator(Of T) ,或者 IEnumerable(Of T) 从其任何一个生成作数到 T 的转换正在缩小。

    3. 将委托放宽到没有签名的委托 -- 如果委托类型为 System.DelegateSystem.MultiCastDelegateSystem.Object

    4. 删除返回或参数委托放宽 -- 如果参数是 AddressOf 或具有声明的返回类型的 lambda,并且委托类型缺少返回类型;或者如果参数是具有一个或多个返回表达式且委托类型缺少返回类型的 lambda;或者如果参数为 AddressOf 或 lambda,并且委托类型具有参数。

    5. 扩大返回类型的委托放宽 -- 如果参数是AddressOf或具有声明的返回类型的 lambda,并且从其返回类型到委托类型的扩展转换,或者该参数是正则 lambda,其中从所有返回表达式到委托返回类型的转换是加宽或标识至少一个加宽;或者,如果参数是异步 lambda 且委托是Task(Of T)Task以及从所有返回表达式到T/Object的转换都是使用至少一个加宽的加宽或标识;或者,如果参数是迭代器 lambda,委托是IEnumerator(Of T)IEnumerable(Of T)IEnumerator或者从所有返回表达式转换到/ObjectT的转换是加宽的,或者IEnumerable至少有一个加宽的标识。

    6. 标识委托放宽 - 如果参数是 AddressOf 与委托完全匹配的或 lambda,则不加宽或缩小或删除参数或返回或生成。接下来,如果集的某些成员不需要缩小转换才适用于任何参数,则消除所有执行作的成员。 例如:

    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. 接下来,根据缩小范围完成消除,如下所示。 (请注意,如果 Option Strict 为 On,则所有需要缩小范围的成员已被判断为不可应用(第 2 步的 适用性参数列表)并删除。

    1. 如果集中的某些实例成员只需要缩小参数表达式类型的 Object转换范围,则消除所有其他成员。
    2. 如果集包含的多个成员只需要缩小 Object范围,则调用目标表达式将重新分类为后期绑定方法访问(如果包含方法组的类型是接口,或者任一适用成员是扩展成员时,则会给出错误)。
    3. 如果有任何候选项只需要从数值文本缩小,则通过以下步骤在所有剩余候选项中选择最具体的候选项。 如果优胜者只需要缩小数值文本的范围,则会选择它作为重载分辨率的结果;否则为错误。

    注意。 此规则的理由是,如果程序被松散类型化(即大多数或全部变量声明为 Object),则当许多转换从 Object 中缩小时,重载解析可能很困难。 在许多情况下(需要对方法调用的参数进行强键入),而不是使重载解析失败,而是将适当的重载方法推迟到运行时。 这样,松散类型调用就可以成功,而无需其他强制转换。 但是,这是一个不幸的副作用是,执行后期绑定调用需要将调用目标强制转换为 Object。 对于结构值,这意味着该值必须装箱为临时值。 如果最终调用的方法尝试更改结构的字段,则此方法返回后,此更改将丢失。 接口被排除在此特殊规则之外,因为后期绑定始终针对运行时类或结构类型的成员解析,这些成员的名称可能与它们实现的接口的成员不同。

  5. 接下来,如果任何实例方法保留在不需要缩小的集中,则从集中消除所有扩展方法。 例如:

    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
    

    注意。 如果存在适用的实例方法以确保添加导入(可能会将新的扩展方法引入范围),则忽略扩展方法不会导致对现有实例方法的调用重新绑定到扩展方法。 鉴于某些扩展方法的广泛范围(即在接口和/或类型参数上定义的方法),这是绑定到扩展方法的更安全方法。

  6. 接下来,如果给定集M的任何两个成员,M并且N给定自变量列表N的成员/类型的节特定性更具体,则从集中消除N。 如果集合中保留多个成员,并且剩余成员在参数列表中不相等,则编译时错误结果。

  7. 否则,给定集的任何两个成员, MN按顺序应用以下断断规则:

    1. 如果 M 参数没有 ParamArray 参数, N 或者如果两者都执行,但 M 将参数传递到 ParamArray 参数比参数 N 少,则从集中消除 N 。 例如:

      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
      

      上面的示例生成以下输出:

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

      注意。 当类使用 paramarray 参数声明方法时,也并不罕见地将一些扩展的窗体作为常规方法包含在内。 为此,可以避免调用具有 paramarray 参数的方法的扩展形式时发生的数组实例的分配。

    2. 如果在 M 派生类型中定义比从 N集中消除 N 的派生类型。 例如:

      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
      

      此规则也适用于定义扩展方法的类型。 例如:

      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. 如果 M 扩展 N 方法和目标类型 M 是类或结构,并且目标类型 N 是接口,则从集中消除 N 。 例如:

      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. 如果MN是扩展方法,并且目标类型MN的类型和类型参数替换后相同,并且类型参数替换之前的目标类型M不包含类型参数,但目标类型的N类型,则类型参数比目标类型N少,从集中消除N。 例如:

      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. 在替换类型参数之前,如果M泛型 (Section Genericity) 小于N集,则从集中消除N

    6. 如果 M 不是扩展方法, N 则从集中消除 N

    7. 如果是M扩展方法,并且M是在 (Section Extension Method Collection)之前N找到的,请从集中消除NN。 例如:

      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
      

      如果在同一步骤中找到扩展方法,则这些扩展方法不明确。 调用始终可以使用包含扩展方法的标准模块的名称和调用扩展方法的名称来消除歧义,就好像它是常规成员一样。 例如:

      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. 如果M和两者都需要类型推理来生成类型参数,并且M不需要确定其任何类型参数的主导类型(即每个类型参数都推断为单个类型),但N确实从集中消除NN

      注意。 此规则可确保在早期版本中成功的重载解析(其中推断类型参数的多个类型会导致错误),继续生成相同的结果。

    9. 如果正在执行重载解析以从 AddressOf 表达式解析委托创建表达式的目标,并且委托和 M 函数都是 N 子例程,请从集中消除 N 。 同样,如果委托和 M 子例程都是函数,则 N 从集中消除 N

    10. 如果未 M 使用任何可选参数默认值代替显式参数,但 N 未使用,则从集中消除 N

    11. 在替换类型参数之前,如果M泛型深度大于N泛型(Section Genericity),则从集中消除N

  8. 否则,调用不明确且发生编译时错误。

给定参数列表的成员/类型的特定性

如果成员M的签名相同,或者每个参数类型与相应参数类型MN相同,则成员被视为N参数列表A相同。

注意。 由于扩展方法,两个成员最终可能位于具有相同签名的方法组中。 由于类型参数或 paramarray 扩展,两个成员也可以同样具体,但没有相同的签名。

成员M被认为比N签名不同时更具体,并且至少有一个参数类型M比参数N类型更具体,并且没有参数类型N比参数M类型更具体。 给定一对参数MjNj与参数Aj匹配的参数,如果下列条件之一为 true,则认为其类型MjNj比以下条件之一更具体

  • 存在从类型 Mj 到类型 Nj之间的扩大转换。 (注意。 由于参数类型与本例中的实际参数无关,因此从常量表达式到数值类型的扩大转换在本例中不考虑。

  • Aj 是文本 0Mj 是数值类型,是 Nj 枚举类型。 (注意。 此规则是必需的,因为文本 0 范围扩大到任何枚举类型。由于枚举类型扩展为其基础类型,这意味着默认情况下,重载解析 0 将优先于枚举类型而不是数值类型。我们收到了很多反馈,认为这种行为是适得其反的。

  • MjNj都是数值类型,并且Mj早于Nj列表ByteSByte、、ShortUIntegerULongLongUShortIntegerDecimal、。 SingleDouble注意。 有关数值类型的规则很有用,因为特定大小的有符号和无符号数值类型之间只有缩小转换。上述规则打破了两种类型之间的平局,而有利于更“自然”的数字类型。在对扩展为特定大小的有符号和无符号数值类型(例如适合这两者的数字文本)进行重载解析时,这一点尤其重要。

  • Mj并且Nj是委托函数类型,并且返回类型的返回类型比 If Aj 的返回类型MjNj更具体,Mj或者被System.Linq.Expressions.Expression(Of T)分类为 lambda 方法,则Nj类型的类型参数(假设它是委托类型)被替换为要比较的类型。

  • Mj 与类型 Aj相同, Nj 并且不是。 (注意。 请注意,上一个规则与 C# 略有不同,因此 C# 要求委托函数类型在比较返回类型之前具有相同的参数列表,而 Visual Basic 则不这样做。

泛型

成员M被确定为小于成员N泛型,如下所示:

  1. 如果对于每个匹配参数对,并且 MjNjNj方法上的类型参数Mj相比,其泛型小于或等于泛型,并且对于方法上的类型参数,至少有一Mj对是泛型参数。
  2. 否则,如果对于每个匹配参数对,并且NjMjNj类型上的类型参数Mj相同或等于泛型,并且对于类型上的类型参数,至少有一Mj对与类型上的类型参数相同,则M泛型小于N泛型。

如果参数M的类型MtNt两者都引用类型参数,或者两者都引用类型参数,则参数被视为与参数N相同的泛型。 M 被视为泛型小于 NMt 引用类型参数且 Nt 不引用泛型参数。

例如:

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

在 currying 期间修复的扩展方法类型参数被视为类型参数,而不是方法上的类型参数。 例如:

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

泛型深度

M如果对于每个匹配参数NjMjMj对,并且具有大于或等于泛型Nj深度,并且至少有一Mj个成员具有更大的泛型深度,则成员确定其型深度大于成员。N 泛型深度的定义如下:

  • 类型参数以外的任何内容都具有比类型参数更深入的泛型;

  • 以递归方式,如果至少一个类型参数具有更深度的泛型,则构造类型具有比另一个构造类型(具有相同类型参数数)的深度,并且没有类型参数比其他类型参数的深度少。

  • 如果第一个数组类型的元素类型比第二个数组类型的元素类型具有更高的泛型深度(维度数相同)。如果第一个数组类型的泛型深度大于第二个数组类型的泛型深度。

例如:

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

参数列表的适用性

如果可以使用参数列表调用方法,则方法 适用于 一组类型参数、位置参数和命名参数。 参数列表与参数列表匹配,如下所示:

  1. 首先,匹配每个位置参数,以便与方法参数列表匹配。 如果位置参数多于参数,并且最后一个参数不是 paramarray,则该方法不适用。 否则,paramarray 参数使用 paramarray 元素类型的参数展开,以匹配位置参数的数目。 如果省略将进入 paramarray 的位置参数,则该方法不适用。
  2. 接下来,将每个命名参数与具有给定名称的参数匹配。 如果其中一个命名参数无法匹配、匹配 paramarray 参数或匹配已与另一个位置或命名参数匹配的参数,则该方法不适用。
  3. 接下来,如果指定了类型参数,则它们与类型参数列表匹配。 如果两个列表的元素数不相同,则该方法不适用,除非类型参数列表为空。 如果类型参数列表为空,则使用类型推理来尝试并推断类型参数列表。 如果类型推理失败,则该方法不适用。 否则,在签名中填充类型参数的位置填充类型参数。如果尚未匹配的参数不是可选的,则该方法不适用。
  4. 如果参数表达式不能隐式转换为它们匹配的参数类型,则该方法不适用。
  5. 如果参数为 ByRef,并且没有从参数类型到参数类型的隐式转换,则该方法不适用。
  6. 如果类型参数违反了方法的约束(包括步骤 3 中推断的类型参数),则该方法不适用。 例如:
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

如果单个参数表达式与 paramarray 参数匹配,并且参数表达式的类型可转换为 paramarray 参数的类型和 paramarray 元素类型,则该方法适用于其扩展和未扩展的形式,但有两个例外。 如果从参数表达式的类型转换为 paramarray 类型正在缩小,则该方法仅适用于其扩展形式。 如果参数表达式是文本 Nothing,则该方法仅适用于其未展开的形式。 例如:

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

上面的示例生成以下输出:

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

在第一个和最后一个调用 F中,正常形式的 F 适用,因为从参数类型到参数类型(两者均为类型 Object())之间的扩大转换,并且参数作为常规值参数传递。 在第二次和第三次调用中,正常形式的 F 不适用,因为不存在从自变量类型到参数类型的扩大转换(从 Object 参数类型到 Object() 收缩转换)。 但是,扩展形式的 F 适用,由调用创建一个元素 Object() 。 数组的单个元素使用给定的参数值(即对数组 Object()的引用)进行初始化。

传递参数,并为可选参数选取参数

如果参数是值参数,则必须将匹配的参数表达式分类为值。 该值将转换为参数的类型,并在运行时作为参数传入。 如果参数是引用参数,并且匹配的参数表达式被归类为类型与参数相同的变量,则在运行时将作为参数传入对变量的引用。

否则,如果将匹配的参数表达式分类为变量、值或属性访问,则会分配参数类型的临时变量。 在运行时调用方法之前,参数表达式将重新分类为值,转换为参数的类型,并分配给临时变量。 然后作为参数传入对临时变量的引用。 计算方法调用后,如果参数表达式被分类为变量或属性访问,则临时变量将分配给变量表达式或属性访问表达式。 如果属性访问表达式没有 Set 访问器,则不会执行分配。

对于未提供参数的可选参数,编译器会按如下所述选取参数。 在所有情况下,它都会在泛型类型替换后针对参数类型进行测试。

  • 如果可选参数具有属性 System.Runtime.CompilerServices.CallerLineNumber,并且调用来自源代码中的位置,并且表示该位置的行号的数字文本具有参数类型的内部转换,则使用数值文本。 如果调用跨越多个行,则要使用的行的选择取决于实现。

  • 如果可选参数具有属性 System.Runtime.CompilerServices.CallerFilePath,并且调用来自源代码中的位置,并且表示该位置的文件路径的字符串文本具有参数类型的内部转换,则使用字符串文本。 文件路径的格式由实现定义。

  • 如果可选参数具有属性 System.Runtime.CompilerServices.CallerMemberName,并且调用位于类型成员的正文中或应用于该类型成员的任何部分的属性中,并且表示该成员名称的字符串文本具有与参数类型的内部转换,则使用字符串文本。 对于属于属性访问器或自定义事件处理程序的调用,使用的成员名称是属性或事件本身的成员名称。 对于属于运算符或构造函数的调用,将使用特定于实现的名称。

如果上述任何内容均不适用,则使用可选参数的默认值(或者 Nothing 未提供默认值)。 如果上述多个应用,则要使用的选择取决于实现。

这些 CallerLineNumber 属性 CallerFilePath 可用于日志记录。 这 CallerMemberName 可用于实现 INotifyPropertyChanged。 下面是一些示例。

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

除了上述可选参数,Microsoft Visual Basic 还识别一些其他可选参数(例如,从 DLL 引用导入):

  • 从元数据导入时,Visual Basic 还会将参数视为指示参数 <Optional> 是可选的:这样就可以导入具有可选参数但没有默认值的 Optional 声明,即使不能使用关键字来表示此参数。

  • 如果可选参数具有特性Microsoft.VisualBasic.CompilerServices.OptionCompareAttribute,并且数字文本 1 或 0 具有对参数类型的转换,则编译器将用作参数 1(如果有效),或者文本 0(如果Option Compare TextOptional Compare Binary有效)。

  • 如果可选参数具有属性 System.Runtime.CompilerServices.IDispatchConstantAttribute,并且具有类型 Object,并且它未指定默认值,则编译器使用参数 New System.Runtime.InteropServices.DispatchWrapper(Nothing)

  • 如果可选参数具有属性 System.Runtime.CompilerServices.IUnknownConstantAttribute,并且具有类型 Object,并且它未指定默认值,则编译器使用参数 New System.Runtime.InteropServices.UnknownWrapper(Nothing)

  • 如果可选参数具有类型 Object,并且未指定默认值,则编译器使用参数 System.Reflection.Missing.Value

条件方法

如果调用表达式引用的目标方法是不是接口的成员的子例程,并且该方法具有一个或多个 System.Diagnostics.ConditionalAttribute 属性,则表达式的计算取决于在该点在源文件中定义的条件编译常量。 属性的每个实例指定一个字符串,该字符串命名条件编译常量。 将评估每个条件编译常量,就像它是条件编译语句的一部分一样。 如果常量计算结果为 True,则表达式在运行时正常计算。 如果常量的计算 False结果为,则根本不计算表达式。

查找属性时,将检查可重写方法的最派生声明。

注意。 特性对函数或接口方法无效,如果对任一方法指定,则忽略此属性。 因此,条件方法只会出现在调用语句中。

类型参数推理

在没有指定类型参数的情况下调用具有类型参数的方法时,将使用 类型参数推理 来尝试并推断调用的类型参数。 这样,当可以推断类型参数时,可以使用更自然的语法来调用具有类型参数的方法。 例如,给定以下方法声明:

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

无需显式指定类型参数即可调用 Choose 该方法:

' 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") 

通过类型参数推理,类型参数 Integer 并从 String 参数确定为方法。

在对参数列表中的 lambda 方法或方法指针执行表达式重新分类 之前 ,会发生类型自变量推理,因为这两种表达式的重新分类可能需要知道参数的类型。 给定一组参数 A1,...,An、一组匹配参数 P1,...,Pn 和一组方法类型参数 T1,...,Tn,首先收集自变量和方法类型参数之间的依赖关系,如下所示:

  • Nothing如果是An文本,则不会生成任何依赖项。

  • 如果是 An lambda 方法,并且其 Pn 类型是构造的委托类型,或者 System.Linq.Expressions.Expression(Of T),其中 T 是构造的委托类型,

  • 如果从相应参数Pn的类型推断 lambda 方法参数的类型,并且参数的类型取决于方法类型参数,则An依赖于Tn该方法类型参数Tn

  • 如果指定 lambda 方法参数的类型,并且相应参数Pn的类型取决于方法类型参数,则Tn依赖于An该方法类型参数Tn

  • 如果返回类型取决于方法类型Pn参数,则Tn依赖于An该方法类型参数Tn

  • 如果是 An 方法指针,并且其 Pn 类型为构造的委托类型,

  • 如果返回类型取决于方法类型Pn参数,则Tn依赖于An该方法类型参数Tn

  • 如果Pn构造的类型和Pn类型依赖于方法类型参数,则Tn依赖于An该方法类型参数Tn

  • 否则,不会生成依赖项。

收集依赖项后,将消除任何没有依赖项的参数。 如果任何方法类型参数没有传出依赖项(即方法类型参数不依赖于参数),则类型推理将失败。 否则,其余参数和方法类型参数将分组到强连接的组件中。 强连接的组件是一组参数和方法类型参数,其中组件中的任何元素都可通过依赖于其他元素来访问。

然后,以拓扑顺序对强连接的组件进行拓扑排序和处理:

  • 如果强类型组件仅包含一个元素,

    • 如果元素已标记为已完成,请跳过它。

    • 如果元素是参数,则向依赖该参数的方法类型参数添加类型提示,并将元素标记为已完成。 如果参数是具有仍需要推断类型的参数的 lambda 方法,则推断 Object 这些参数的类型。

    • 如果元素是方法类型参数,则将方法类型参数推断为参数类型提示中的主导类型,并将元素标记为已完成。 如果类型提示对数组元素有限制,则只考虑在给定类型的数组之间有效的转换(即协变和内部数组转换)。 如果类型提示具有泛型参数限制,则仅考虑标识转换。 如果无法选择主导类型,推理将失败。 如果任何 lambda 方法参数类型依赖于此方法类型参数,则类型将传播到 lambda 方法。

  • 如果强类型组件包含多个元素,则组件包含一个周期。

    • 对于组件中作为元素的每个方法类型参数,如果方法类型参数依赖于未标记为完成的参数,请将该依赖项转换为将在推理过程结束时检查的断言。

    • 在确定强类型组件时重启推理过程。

如果所有方法类型参数的类型推理成功,则会检查已更改为断言的任何依赖项。 如果参数的类型隐式转换为方法类型参数的推断类型,则断言成功。 如果断言失败,则类型参数推理失败。

给定参数的参数类型和Ta参数P类型的参数类型Tp,类型提示将A按如下所示生成:

  • 如果没有 Tp 涉及任何方法类型参数,则不会生成任何提示。

  • 如果Tp并且Ta是相同排名的数组类型,请替换为TaTp元素Ta类型的元素类型,Tp并使用数组元素限制重启此过程。

  • 如果是 Tp 方法类型参数,则 Ta 添加为具有当前限制的类型提示(如果有)。

  • 如果A为 lambda 方法,并且Tp是构造的委托类型,或者System.Linq.Expressions.Expression(Of T)(其中T是构造的委托类型),则对于每个 lambda 方法参数类型和TL相应的委托参数类型TD,请替换为TaTLTp替换为TD并重启进程,且没有任何限制。 然后,替换为 Ta lambda 方法的返回类型,并:

    • 如果 A 为常规 lambda 方法,请替换为 Tp 委托类型的返回类型;
    • 如果A为异步 lambda 方法,并且委托类型的返回类型具有某种T形式的形式Task(Of T),则替换为TpT类型;
    • 如果 A 为迭代器 lambda 方法,并且委托类型的返回类型具有窗体 IEnumerator(Of T)IEnumerable(Of T) 某些 T类型,则替换为 TpT类型。
    • 接下来,重启没有限制的进程。
  • 如果 A 方法是方法指针,并且 Tp 是构造的委托类型,请使用参数类型 Tp 来确定哪个方法指向最适用 Tp。 如果有最适用的方法,请替换为方法的返回类型,Tp并替换为Ta委托类型的返回类型,并重启进程,且没有任何限制。

  • 否则, Tp 必须是构造的类型。 给定 TG的泛型类型 Tp

    • 如果是Ta,则从TG中继承或完全实现一次类型TG,则对于每个匹配的类型自变量TaTpxTpTax,请Ta替换为TaxTpTpx泛型参数限制来重启进程。TG

    • 否则,泛型方法的类型推理失败。

类型推理的成功本身并不保证该方法适用。