通过


类型成员

类型成员定义存储位置和可执行代码。 它们可以是方法、构造函数、事件、常量、变量和属性。

接口方法实现

方法、事件和属性可以实现接口成员。 若要实现接口成员,成员声明指定 Implements 关键字并列出一个或多个接口成员。

ImplementsClause
    : ( 'Implements' ImplementsList )?
    ;

ImplementsList
    : InterfaceMemberSpecifier ( Comma InterfaceMemberSpecifier )*
    ;

InterfaceMemberSpecifier
    : NonArrayTypeName Period IdentifierOrKeyword
    ;

除非声明为MustOverrideOverridable重写其他成员,否则实现接口成员的方法和属性是隐式NotOverridable的。 实现接口成员的成员是 Shared错误的。 成员的可访问性对其实现接口成员的能力没有影响。

要使接口实现有效,包含类型的实现列表必须命名包含兼容成员的接口。 兼容成员是其签名与实现成员的签名匹配的成员。 如果正在实现泛型接口,则在检查兼容性时,Implements 子句中提供的类型参数将替换为签名。 例如:

Interface I1(Of T)
    Sub F(x As T)
End Interface

Class C1
    Implements I1(Of Integer)

    Sub F(x As Integer) Implements I1(Of Integer).F
    End Sub
End Class

Class C2(Of U)
    Implements I1(Of U)

    Sub F(x As U) Implements I1(Of U).F
    End Sub
End Class

如果使用委托类型声明的事件正在实现接口事件,则兼容事件是其基础委托类型为相同类型的事件。 否则,该事件使用它正在实现的接口事件中的委托类型。 如果此类事件实现多个接口事件,则所有接口事件必须具有相同的基础委托类型。 例如:

Interface ClickEvents
    Event LeftClick(x As Integer, y As Integer)
    Event RightClick(x As Integer, y As Integer)
End Interface

Class Button
    Implements ClickEvents

    ' OK. Signatures match, delegate type = ClickEvents.LeftClickHandler.
    Event LeftClick(x As Integer, y As Integer) _
        Implements ClickEvents.LeftClick

    ' OK. Signatures match, delegate type = ClickEvents.RightClickHandler.
    Event RightClick(x As Integer, y As Integer) _
        Implements ClickEvents.RightClick
End Class

Class Label
    Implements ClickEvents

    ' Error. Signatures match, but can't be both delegate types.
    Event Click(x As Integer, y As Integer) _
        Implements ClickEvents.LeftClick, ClickEvents.RightClick
End Class

实现列表中的接口成员是使用类型名称、句点和标识符指定的。 类型名称必须是实现列表中的接口或实现列表中的接口基接口,标识符必须是指定接口的成员。 单个成员可以实现多个匹配的接口成员。

Interface ILeft
    Sub F()
End Interface

Interface IRight
    Sub F()
End Interface

Class Test
    Implements ILeft, IRight

    Sub F() Implements ILeft.F, IRight.F
    End Sub
End Class

如果由于多个接口继承,所实现的接口成员在所有显式实现的接口中不可用,则实现成员必须显式引用该成员可用的基接口。 例如,如果I1并包含成员M,并且I3继承自I1I2,则实现I3的类型将实现I1.MI2.M实现。I2 如果接口阴影将继承的成员相乘,则实现类型必须实现继承的成员及其阴影成员。

Interface ILeft
    Sub F()
End Interface

Interface IRight
    Sub F()
End Interface

Interface ILeftRight
    Inherits ILeft, IRight

    Shadows Sub F()
End Interface

Class Test
    Implements ILeftRight

    Sub LeftF() Implements ILeft.F
    End Sub

    Sub RightF() Implements IRight.F
    End Sub

    Sub LeftRightF() Implements ILeftRight.F
    End Sub
End Class

如果实现接口成员的包含接口是泛型的,则必须提供与要实现的接口相同的类型参数。 例如:

Interface I1(Of T)
    Function F() As T
End Interface

Class C1
    Implements I1(Of Integer)
    Implements I1(Of Double)

    Function F1() As Integer Implements I1(Of Integer).F
    End Function

    Function F2() As Double Implements I1(Of Double).F
    End Function

    ' Error: I1(Of String) is not implemented by C1
    Function F3() As String Implements I1(Of String).F
    End Function
End Class

Class C2(Of U)
    Implements I1(Of U)

    Function F() As U Implements I1(Of U).F
    End Function
End Class

方法

方法包含程序的可执行语句。

MethodMemberDeclaration
    : MethodDeclaration
    | ExternalMethodDeclaration
    ;

InterfaceMethodMemberDeclaration
    : InterfaceMethodDeclaration
    ;

MethodDeclaration
    : SubDeclaration
    | MustOverrideSubDeclaration
    | FunctionDeclaration
    | MustOverrideFunctionDeclaration
    ;

InterfaceMethodDeclaration
    : InterfaceSubDeclaration
    | InterfaceFunctionDeclaration
    ;

SubSignature
    : 'Sub' Identifier TypeParameterList?
      ( OpenParenthesis ParameterList? CloseParenthesis )?
    ;

FunctionSignature
    : 'Function' Identifier TypeParameterList?
      ( OpenParenthesis ParameterList? CloseParenthesis )?
      ( 'As' Attributes? TypeName )?
    ;

SubDeclaration
    : Attributes? ProcedureModifier* SubSignature
      HandlesOrImplements? LineTerminator
      Block
      'End' 'Sub' StatementTerminator
    ;

MustOverrideSubDeclaration
    : Attributes? MustOverrideProcedureModifier+ SubSignature
      HandlesOrImplements? StatementTerminator
    ;

InterfaceSubDeclaration
    : Attributes? InterfaceProcedureModifier* SubSignature StatementTerminator
    ;

FunctionDeclaration
    : Attributes? ProcedureModifier* FunctionSignature
      HandlesOrImplements? LineTerminator
      Block
      'End' 'Function' StatementTerminator
    ;

MustOverrideFunctionDeclaration
    : Attributes? MustOverrideProcedureModifier+ FunctionSignature
      HandlesOrImplements? StatementTerminator
    ;

InterfaceFunctionDeclaration
    : Attributes? InterfaceProcedureModifier* FunctionSignature StatementTerminator
    ;

ProcedureModifier
    : AccessModifier | 'Shadows' | 'Shared' | 'Overridable' | 'NotOverridable' | 'Overrides'
    | 'Overloads' | 'Partial' | 'Iterator' | 'Async'
    ;

MustOverrideProcedureModifier
    : ProcedureModifier
    | 'MustOverride'
    ;

InterfaceProcedureModifier
    : 'Shadows' | 'Overloads'
    ;

HandlesOrImplements
    : HandlesClause
    | ImplementsClause
    ;

具有可选参数列表和可选返回值的方法要么共享,要么不共享。 通过类或类的实例访问共享方法。 通过类的实例访问非共享方法(也称为实例方法)。 以下示例演示了一个类,该类Stack具有多个共享方法(CloneFlip),以及多个实例方法(PushPopToString):

Public Class Stack
    Public Shared Function Clone(s As Stack) As Stack
        ...
    End Function

    Public Shared Function Flip(s As Stack) As Stack
        ...
    End Function

    Public Function Pop() As Object
        ...
    End Function

    Public Sub Push(o As Object)
        ...
    End Sub 

    Public Overrides Function ToString() As String
        ...
    End Function 
End Class 

Module Test
    Sub Main()
        Dim s As Stack = New Stack()
        Dim i As Integer

        While i < 10
            s.Push(i)
        End While

        Dim flipped As Stack = Stack.Flip(s)
        Dim cloned As Stack = Stack.Clone(s)

        Console.WriteLine("Original stack: " & s.ToString())
        Console.WriteLine("Flipped stack: " & flipped.ToString())
        Console.WriteLine("Cloned stack: " & cloned.ToString())
    End Sub
End Module

方法可以重载,这意味着只要方法具有唯一签名,多个方法可能具有相同的名称。 方法的签名包含其参数的数量和类型。 具体来说,方法的签名不包括返回类型或参数修饰符,如 Optional、ByRef 或 ParamArray。 以下示例演示一个包含多个重载的类:

Module Test
    Sub F()
        Console.WriteLine("F()")
    End Sub 

    Sub F(o As Object)
        Console.WriteLine("F(Object)")
    End Sub

    Sub F(value As Integer)
        Console.WriteLine("F(Integer)")
    End Sub 

    Sub F(a As Integer, b As Integer)
        Console.WriteLine("F(Integer, Integer)")
    End Sub 

    Sub F(values() As Integer)
        Console.WriteLine("F(Integer())")
    End Sub 

    Sub G(s As String, Optional s2 As String = 5)
        Console.WriteLine("G(String, Optional String")
    End Sub

    Sub G(s As String)
        Console.WriteLine("G(String)")
    End Sub


    Sub Main()
        F()
        F(1)
        F(CType(1, Object))
        F(1, 2)
        F(New Integer() { 1, 2, 3 })
        G("hello")
        G("hello", "world")
    End Sub
End Module

程序的输出为:

F()
F(Integer)
F(Object)
F(Integer, Integer)
F(Integer())
G(String)
G(String, Optional String)

仅在可选参数中不同的重载可用于库的“版本控制”。 例如,库的 v1 可能包含具有可选参数的函数:

Sub fopen(fileName As String, Optional accessMode as Integer = 0)

然后,该库的 v2 想要添加另一个可选参数“password”,并且它想要这样做而不破坏源兼容性(因此,用于目标 v1 的应用程序可以重新编译),并且无需中断二进制兼容性(因此用于引用 v1 的应用程序现在可以在不重新编译的情况下引用 v2)。 这是 v2 的外观:

Sub fopen(file As String, mode as Integer)
Sub fopen(file As String, Optional mode as Integer = 0, Optional pword As String = "")

请注意,公共 API 中的可选参数不符合 CLS。 但是,它们至少可由 Visual Basic 和 C#4 和 F# 使用。

常规、异步和迭代器方法声明

有两种类型的方法: 子例程(不返回值)和 函数。 只有在方法在接口中定义或具有MustOverride修饰符时,才能省略方法的正文和End构造。 如果未在函数上指定任何返回类型,并且正在使用严格的语义,则会发生编译时错误;否则,类型为隐式 Object 类型或方法的类型字符的类型。 方法的返回类型和参数类型的辅助功能域必须与方法本身的辅助功能域或超集相同。

常规方法既不是修饰符,Iterator也不是Async修饰符。 它可以是子例程或函数。 “常规方法”部分详细介绍了调用常规方法时会发生什么情况。

迭代器方法是具有Iterator修饰符且无Async修饰符的方法。 它必须是函数,并且其返回类型必须为IEnumeratorIEnumerableIEnumerable(Of T)IEnumerator(Of T)某些T函数,并且必须没有ByRef参数。 “ 迭代器方法” 部分详细介绍了调用迭代器方法时会发生什么情况。

异步方法是具有Async修饰符且无Iterator修饰符的方法。 它必须是子例程,或者是具有返回类型的 Task 函数或 Task(Of T) 某些 T函数,并且必须没有 ByRef 参数。 Async 方法部分详细介绍了调用异步方法时会发生什么情况。

如果方法不是这三种方法之一,则为编译时错误。

子例程和函数声明特别,其开头和结束语句必须从逻辑行的开头开始。 此外,非MustOverride 子例程或函数声明的正文必须从逻辑行的开头开始。 例如:

Module Test
    ' Illegal: Subroutine doesn't start the line
    Public x As Integer : Sub F() : End Sub

    ' Illegal: First statement doesn't start the line
    Sub G() : Console.WriteLine("G")
    End Sub

    ' Illegal: End Sub doesn't start the line
    Sub H() : End Sub
End Module

外部方法声明

外部方法声明引入了一个新方法,该方法的实现在程序外部提供。

ExternalMethodDeclaration
    : ExternalSubDeclaration
    | ExternalFunctionDeclaration
    ;

ExternalSubDeclaration
    : Attributes? ExternalMethodModifier* 'Declare' CharsetModifier? 'Sub'
      Identifier LibraryClause AliasClause?
      ( OpenParenthesis ParameterList? CloseParenthesis )? StatementTerminator
    ;

ExternalFunctionDeclaration
    : Attributes? ExternalMethodModifier* 'Declare' CharsetModifier? 'Function'
      Identifier LibraryClause AliasClause?
      ( OpenParenthesis ParameterList? CloseParenthesis )?
      ( 'As' Attributes? TypeName )?
      StatementTerminator
    ;

ExternalMethodModifier
    : AccessModifier
    | 'Shadows'
    | 'Overloads'
    ;

CharsetModifier
    : 'Ansi' | 'Unicode' | 'Auto'
    ;

LibraryClause
    : 'Lib' StringLiteral
    ;

AliasClause
    : 'Alias' StringLiteral
    ;

由于外部方法声明不提供实际实现,因此它没有方法正文或 End 构造。 外部方法是隐式共享的,可能没有类型参数,也不能处理事件或实现接口成员。 如果未在函数上指定任何返回类型,并且正在使用严格的语义,则会发生编译时错误。 否则,类型为隐式 Object 类型或方法的类型字符的类型。 外部方法的返回类型和参数类型的辅助功能域必须与外部方法本身的辅助功能域或超集相同。

外部方法声明的库子句指定实现该方法的外部文件的名称。 可选别名子句是一个字符串,指定外部文件中方法的数字序号(以 # 字符为前缀)或名称。 还可以指定单字符集修饰符,该修饰符控制在调用外部方法期间用于封送字符串的字符集。 Unicode修饰符将所有字符串封送给 Unicode 值,Ansi修饰符将所有字符串封送给 ANSI 值,并且Auto修饰符根据 .NET Framework 规则根据方法的名称封送字符串,或者指定别名。 如果未指定修饰符,则默认值为 Ansi

如果 Ansi 指定或 Unicode 已指定,则在外部文件中查找方法名称,无需修改。 如果 Auto 已指定,则方法名称查找取决于平台。 如果平台被视为 ANSI(例如 Windows 95、Windows 98、Windows ME),则会查找方法名称,无需修改。 如果查找失败,则会追加一个 A ,并再次尝试查找。 如果平台被视为 Unicode(例如 Windows NT、Windows 2000、Windows XP),则会追加 a 并 W 查找名称。 如果查找失败,则再次尝试查找而不 W尝试查找 。 例如:

Module Test
    ' All platforms bind to "ExternSub".
    Declare Ansi Sub ExternSub Lib "ExternDLL" ()

    ' All platforms bind to "ExternSub".
    Declare Unicode Sub ExternSub Lib "ExternDLL" ()

    ' ANSI platforms: bind to "ExternSub" then "ExternSubA".
    ' Unicode platforms: bind to "ExternSubW" then "ExternSub".
    Declare Auto Sub ExternSub Lib "ExternDLL" ()
End Module

传递给外部方法的数据类型根据 .NET Framework 数据封送约定进行封送处理,但有一个例外。 值(即 ByVal x As String)传递的字符串变量会封送至 OLE 自动化 BSTR 类型,对外部方法中的 BSTR 所做的更改将反映在字符串参数中。 这是因为外部方法中的类型 String 是可变的,这种特殊的封送会模拟该行为。 通过引用传递的字符串参数(即 ByRef x As String)作为指向 OLE 自动化 BSTR 类型的指针进行封送。 可以通过在参数上指定 System.Runtime.InteropServices.MarshalAsAttribute 属性来替代这些特殊行为。

此示例演示如何使用外部方法:

Class Path
    Declare Function CreateDirectory Lib "kernel32" ( _
        Name As String, sa As SecurityAttributes) As Boolean
    Declare Function RemoveDirectory Lib "kernel32" ( _
        Name As String) As Boolean
    Declare Function GetCurrentDirectory Lib "kernel32" ( _
        BufSize As Integer, Buf As String) As Integer
    Declare Function SetCurrentDirectory Lib "kernel32" ( _
        Name As String) As Boolean
End Class

可重写的方法

Overridable修饰符指示方法可重写。 Overrides修饰符指示方法重写具有相同签名的基类型可重写方法。 NotOverridable修饰符指示无法进一步重写可重写的方法。 修饰 MustOverride 符指示必须在派生类中重写方法。

这些修饰符的某些组合无效:

  • Overridable 并且 NotOverridable 是相互排斥的,不能组合。

  • MustOverride Overridable 表示(因此不能指定它),不能与 NotOverridable.

  • NotOverridable 不能与 OverridableMustOverride 必须组合在一起 Overrides

  • Overrides Overridable 表示(因此不能指定它),不能与 MustOverride.

还可以对可重写的方法进行其他限制:

  • 方法 MustOverride 可能不包含方法正文或 End 构造,不能重写其他方法,并且可能仅出现在类中 MustInherit

  • 如果方法指定 Overrides 且没有要重写的匹配基方法,则会发生编译时错误。 重写方法可能未指定 Shadows

  • 如果重写方法的辅助功能域不等于所重写方法的辅助功能域,则方法可能不会重写另一种方法。 一个例外是,重写另一 Protected Friend 个程序集中没有 Friend 访问权限的方法必须指定 Protected (不是 Protected Friend)。

  • Private 方法可能 Overridable不是, NotOverridable或者 MustOverride,也可能替代其他方法。

  • 类中 NotInheritable 的方法可能未声明 OverridableMustOverride

以下示例说明了可重写方法与不可重写方法之间的差异:

Class Base
    Public Sub F()
        Console.WriteLine("Base.F")
    End Sub

    Public Overridable Sub G()
        Console.WriteLine("Base.G")
    End Sub
End Class

Class Derived
    Inherits Base

    Public Shadows Sub F()
        Console.WriteLine("Derived.F")
    End Sub

    Public Overrides Sub G()
        Console.WriteLine("Derived.G")
    End Sub
End Class

Module Test
    Sub Main()
        Dim d As Derived = New Derived()
        Dim b As Base = d

        b.F()
        d.F()
        b.G()
        d.G()
    End Sub
End Module

在示例中,类Base引入了方法和FOverridable方法G。 该类 Derived 引入了一个新方法 F,从而隐藏继承 F的方法,并重写继承的方法 G。 此示例生成以下输出:

Base.F
Derived.F
Derived.G
Derived.G

请注意,语句 b.G() 会调用 Derived.G 而不是 Base.G。 这是因为实例(即)的运行时类型(即 Derived)而不是实例(即 Base)的编译时类型决定了要调用的实际方法实现。

共享方法

修饰 Shared 符指示方法是 共享方法。 共享方法不对类型的特定实例进行作,可以直接从类型而不是通过类型的特定实例调用。 但是,使用实例限定共享方法是有效的。 引用MeMyClassMyBase共享方法无效。 共享方法可能 Overridable不是, NotOverridable或者 MustOverride,它们可能不会重写方法。 在标准模块和接口中定义的方法可能不会指定,因为它们已Shared隐式指定Shared

在没有 Shared 修饰符的结构或类中声明的方法是 实例方法。 实例方法对类型的给定实例进行作。 实例方法只能通过类型的实例调用,并且可以通过表达式引用实例 Me

以下示例演示了访问共享成员和实例成员的规则:

Class Test
    Private x As Integer
    Private Shared y As Integer

    Sub F()
        x = 1 ' Ok, same as Me.x = 1.
        y = 1 ' Ok, same as Test.y = 1.
    End Sub

    Shared Sub G()
        x = 1 ' Error, cannot access Me.x.
        y = 1 ' Ok, same as Test.y = 1.
    End Sub

    Shared Sub Main()
        Dim t As Test = New Test()

        t.x = 1 ' Ok.
        t.y = 1 ' Ok.
        Test.x = 1 ' Error, cannot access instance member through type.
        Test.y = 1 ' Ok.
    End Sub
End Class

方法 F 显示,在实例函数成员中,标识符可用于访问实例成员和共享成员。 方法 G 显示,在共享函数成员中,通过标识符访问实例成员是错误的。 方法 Main 显示,在成员访问表达式中,必须通过实例访问实例成员,但可以通过类型或实例访问共享成员。

方法参数

参数是一个变量,可用于将信息传入和传出方法。 方法的参数列表由方法的参数列表声明,由一个或多个用逗号分隔的参数组成。

ParameterList
    : Parameter ( Comma Parameter )*
    ;

Parameter
    : Attributes? ParameterModifier* ParameterIdentifier ( 'As' TypeName )?
      ( Equals ConstantExpression )?
    ;

ParameterModifier
    : 'ByVal' | 'ByRef' | 'Optional' | 'ParamArray'
    ;

ParameterIdentifier
    : Identifier IdentifierModifiers
    ;

如果未为参数指定类型,并且使用了严格的语义,则会发生编译时错误。 否则,默认类型为 Object 或参数的类型字符的类型。 即使在宽松语义下,如果一个参数包含子 As 句,则所有参数都必须指定类型。

参数分别由修饰符ByValByRefOptional、和 ParamArray 指定为值、引用、可选或参数。 未指定 ByRefByVal 默认为 ByVal的参数。

参数名称的范围限定为方法的整个正文,并且始终可公开访问。 方法调用创建特定于该调用的参数的副本,以及调用的参数列表为新创建的参数赋值或变量引用。 由于外部方法声明和委托声明没有正文,因此在参数列表中允许重复的参数名称,但不建议这样做。

标识符后跟可为 null 的名称修饰符 ? ,以指示该标识符可为 null,还后跟数组名称修饰符,以指示它是数组。 它们可以组合在一起,例如“ByVal x?() As Integer”。 不允许使用显式数组边界;此外,如果存在可为 null 的名称修饰符,则必须存在子 As 句。

值参数

使用显式ByVal修饰符声明值参数ByVal如果使用修饰符,ByRef则可能不会指定修饰符。 值参数通过参数所属的成员调用而存在,并使用调用中给定的参数值进行初始化。 返回成员后,值参数将停止存在。

允许给值参数分配新值的方法。 此类分配仅影响值参数表示的本地存储位置;它们对方法调用中给出的实际参数没有影响。

当参数的值传递到方法中并且参数的修改不会影响原始参数时,将使用值参数。 值参数引用其自己的变量,该变量不同于相应参数的变量。 通过复制相应参数的值来初始化此变量。 下面的示例演示了一个名为 p 的值参数的方法F

Module Test
    Sub F(p As Integer)
        Console.WriteLine("p = " & p)
        p += 1
    End Sub 

    Sub Main()
        Dim a As Integer = 1

        Console.WriteLine("pre: a = " & a)
        F(a)
        Console.WriteLine("post: a = " & a)
    End Sub
End Module

该示例生成以下输出,即使修改了值参数 p

pre: a = 1
p = 1
post: a = 1

引用参数

引用参数是用修饰符声明的参数 ByRefByRef如果指定了修饰符,ByVal则不能使用修饰符。 引用参数不会创建新的存储位置。 相反,引用参数表示在方法或构造函数调用中作为参数给定的变量。 从概念上讲,引用参数的值始终与基础变量相同。

引用参数采用两种模式,可以是 别名 ,要么通过 复制回复制。

别名。 当参数充当调用方提供的参数的别名时,将使用引用参数。 引用参数本身不定义变量,而是引用相应参数的变量。 直接修改引用参数,并立即影响相应的参数。 以下示例演示了一个具有两个引用参数的方法 Swap

Module Test
    Sub Swap(ByRef a As Integer, ByRef b As Integer)
        Dim t As Integer = a
        a = b
        b = t
    End Sub 

    Sub Main()
        Dim x As Integer = 1
        Dim y As Integer = 2

        Console.WriteLine("pre: x = " & x & ", y = " & y)
        Swap(x, y)
        Console.WriteLine("post: x = " & x & ", y = " & y)
    End Sub 
End Module

程序的输出为:

pre: x = 1, y = 2
post: x = 2, y = 1

对于类中方法Swap的调用,a表示x,b表示yMain 因此,此调用的效果是交换 xy 的值。

在采用引用参数的方法中,多个名称可以表示相同的存储位置:

Module Test
    Private s As String

    Sub F(ByRef a As String, ByRef b As String)
        s = "One"
        a = "Two"
        b = "Three"
    End Sub

    Sub G()
        F(s, s)
    End Sub
End Module

在示例中,调用方法FG将同时传递对s两者的a引用和 b。 因此,对于该调用,名称s以及ab所有引用相同的存储位置,以及三个赋值都修改实例变量s

复制回复制。 如果传递给引用参数的变量的类型与引用参数的类型不兼容,或者将非变量(例如属性)作为参数传递给引用参数,或者调用是后期绑定的,则会分配临时变量并将其传递给引用参数。 在调用方法之前,传入的值将复制到此临时变量中,并在方法返回时将复制回原始变量(如果有一个变量以及是否可写)。 因此,引用参数不一定包含对传入的变量的确切存储的引用,在方法退出之前,对引用参数的任何更改可能不会反映在变量中。 例如:

Class Base
End Class

Class Derived
    Inherits Base
End Class

Module Test
    Sub F(ByRef b As Base)
        b = New Base()
    End Sub

    Property G() As Base
        Get
        End Get
        Set
        End Set
    End Property

    Sub Main()
        Dim d As Derived

        F(G)   ' OK.
        F(d)   ' Throws System.InvalidCastException after F returns.
    End Sub
End Module

在第一次调用 F的情况下,将创建一个临时变量,并将属性 G 的值赋给它并传入 F。 返回 F后,临时变量中的值将赋回其属性 G。 第二种情况下,将创建另一个临时变量,并向其赋值 d 并传入 F。 从 F中返回时,临时变量中的值将转换回变量的类型, Derived并赋给 d该变量。 由于传递回的值不能强制转换为 Derived,因此在运行时会引发异常。

可选参数

使用修饰符声明 Optional 可选参数。 在正式参数列表中遵循可选参数的参数也必须是可选的;未能在以下参数上指定 Optional 修饰符将触发编译时错误。 某些类型可为 null 的类型 T? 或不可为 null 类型的 T 可选参数必须指定一个常量表达式 e ,如果未指定任何参数,则用作默认值。 如果 e 计算结果为 Nothing Object 类型,则 参数类型的 默认值将用作参数的默认值。 否则, CType(e, T) 必须是常量表达式,并且它被视为参数的默认值。

可选参数是参数初始值设定项有效的唯一情况。 初始化始终作为调用表达式的一部分完成,而不是在方法正文本身中完成。

Module Test
    Sub F(x As Integer, Optional y As Integer = 20)
        Console.WriteLine("x = " & x & ", y = " & y)
    End Sub

    Sub Main()
        F(10)
        F(30,40)
    End Sub
End Module

程序的输出为:

x = 10, y = 20
x = 30, y = 40

可选参数不能在委托或事件声明中指定,也不能在 lambda 表达式中指定。

ParamArray 参数

ParamArray 参数使用 ParamArray 修饰符声明。 ParamArray如果存在修饰符,ByVal则必须指定修饰符,并且没有其他参数可以使用ParamArray修饰符。 参数 ParamArray 的类型必须是一维数组,并且它必须是参数列表中的最后一个参数。

参数 ParamArray 表示类型的不确定参数 ParamArray数。 在方法本身中, ParamArray 参数被视为其声明的类型,并且没有特殊语义。 参数 ParamArray 是隐式可选的,默认值为空的一维数组的类型 ParamArray

ParamArray允许在方法调用中通过以下两种方式之一指定参数:

  • 为某一类型 ParamArray 提供的参数可以是扩展为类型的 ParamArray 单个表达式。 在这种情况下,行为 ParamArray 与值参数类似。

  • 或者,调用可以为 <a0/> 指定零个或多个参数,其中每个参数都是可隐式转换为元素类型的类型的表达式。 在这种情况下,调用将创建一个类型实例,该实例 ParamArray 的长度对应于参数数,使用给定的参数值初始化数组实例的元素,并使用新创建的数组实例作为实际参数。

除了在调用中允许可变数量的参数外,一个 ParamArray 参数与同一类型的值参数完全等效,如以下示例所示。

Module Test
    Sub F(ParamArray args() As Integer)
        Dim i As Integer

        Console.Write("Array contains " & args.Length & " elements:")
        For Each i In args
            Console.Write(" " & i)
        Next i
        Console.WriteLine()
    End Sub

    Sub Main()
        Dim a As Integer() = { 1, 2, 3 }

        F(a)
        F(10, 20, 30, 40)
        F()
    End Sub
End Module

该示例生成输出

Array contains 3 elements: 1 2 3
Array contains 4 elements: 10 20 30 40
Array contains 0 elements:

第一个调用 F 只是将数组 a 作为值参数传递。 第二次调用 F 自动创建具有给定元素值的四个元素数组,并将该数组实例作为值参数传递。 同样,第三次调用 F 将创建一个零元素数组,并将该实例作为值参数传递。 第二次与第三次调用完全等同于写入:

F(New Integer() {10, 20, 30, 40})
F(New Integer() {})

ParamArray 不能在委托或事件声明中指定参数。

事件处理

方法可以以声明方式处理实例或共享变量中的对象引发的事件。 为了处理事件,方法声明指定 Handles 关键字并列出一个或多个事件。

HandlesClause
    : ( 'Handles' EventHandlesList )?
    ;

EventHandlesList
    : EventMemberSpecifier ( Comma EventMemberSpecifier )*
    ;

EventMemberSpecifier
    : Identifier Period IdentifierOrKeyword
    | 'MyBase' Period IdentifierOrKeyword
    | 'MyClass' Period IdentifierOrKeyword
    | 'Me' Period IdentifierOrKeyword
    ;

列表中的事件 Handles 由两个标识符指定,用句点分隔:

  • 第一个标识符必须是指定修饰符或MyBaseMyClassMe或关键字的包含类型的WithEvents实例或共享变量;否则会发生编译时错误。 此变量包含将引发此方法处理的事件的对象。

  • 第二个标识符必须指定第一个标识符类型的成员。 成员必须是事件,并且可以共享。 如果为第一个标识符指定了共享变量,则必须共享事件或错误结果。

如果语句AddHandler E, AddressOf M也有效,则处理程序方法M被视为事件E的有效事件处理程序。 AddHandler但是,与语句不同,显式事件处理程序允许使用不带参数的方法处理事件,而不管是否使用严格的语义:

Option Strict On

Class C1
    Event E(x As Integer)
End Class

Class C2
    withEvents C1 As New C1()

    ' Valid
    Sub M1() Handles C1.E
    End Sub

    Sub M2()
        ' Invalid
        AddHandler C1.E, AddressOf M1
    End Sub
End Class

单个成员可以处理多个匹配事件,多个方法可以处理单个事件。 方法的可访问性对其处理事件的能力没有影响。 以下示例演示如何处理事件的方法:

Class Raiser
    Event E1()

    Sub Raise()
        RaiseEvent E1
    End Sub
End Class

Module Test
    WithEvents x As Raiser

    Sub E1Handler() Handles x.E1
        Console.WriteLine("Raised")
    End Sub

    Sub Main()
        x = New Raiser()
        x.Raise()
        x.Raise()
    End Sub
End Module

这将输出:

Raised
Raised

类型继承其基类型提供的所有事件处理程序。 派生类型不能以任何方式更改它从基类型继承的事件映射,但可能会向事件添加其他处理程序。

扩展方法

可以使用 扩展方法将方法添加到类型声明外部的类型。 扩展方法是应用了属性的方法 System.Runtime.CompilerServices.ExtensionAttribute 。 它们只能在标准模块中声明,并且必须至少有一个参数,该参数指定方法扩展的类型。 例如,以下扩展方法扩展类型 String

Imports System.Runtime.CompilerServices

Module StringExtensions
    <Extension> _
    Sub Print(s As String)
        Console.WriteLine(s)
    End Sub
End Module

注意。 尽管 Visual Basic 要求在标准模块中声明扩展方法,但 C# 等其他语言可能允许在其他类型的类型中声明它们。 只要方法遵循此处概述的其他约定,并且包含类型不是开放泛型类型且无法实例化,Visual Basic 就会识别扩展方法。

调用扩展方法时,将调用它的实例传递给第一个参数。 无法声明 Optional 第一个参数或 ParamArray。 任何类型(包括类型参数)都可以显示为扩展方法的第一个参数。 例如,以下方法扩展了类型 Integer()、实现 System.Collections.Generic.IEnumerable(Of T)的任何类型的以及任何类型的类型:

Imports System.Runtime.CompilerServices

Module Extensions
    <Extension> _
    Sub PrintArray(a() As Integer)
        ...
    End Sub

    <Extension> _
    Sub PrintList(Of T)(a As IEnumerable(Of T))
        ...
    End Sub

    <Extension> _
    Sub Print(Of T)(a As T)
        ...
    End Sub
End Module

如前面的示例所示,可以扩展接口。 接口扩展方法提供方法的实现,因此实现具有扩展方法的接口的类型仍然只实现接口最初声明的成员。 例如:

Imports System.Runtime.CompilerServices

Interface IAction
  Sub DoAction()
End Interface

Module IActionExtensions 
    <Extension> _
    Public Sub DoAnotherAction(i As IAction) 
        i.DoAction()
    End Sub
End Module

Class C
  Implements IAction

  Sub DoAction() Implements IAction.DoAction
    ...
  End Sub

  ' ERROR: Cannot implement extension method IAction.DoAnotherAction
  Sub DoAnotherAction() Implements IAction.DoAnotherAction
    ...
  End Sub
End Class

扩展方法还可以对其类型参数具有类型约束,与非扩展泛型方法一样,可以推断类型参数:

Imports System.Runtime.CompilerServices

Module IEnumerableComparableExtensions
    <Extension> _
    Public Function Sort(Of T As IComparable(Of T))(i As IEnumerable(Of T)) _
        As IEnumerable(Of T)
        ...
    End Function
End Module

还可以通过扩展类型的隐式实例表达式来访问扩展方法:

Imports System.Runtime.CompilerServices

Class C1
    Sub M1()
        Me.M2()
        M2()
    End Sub
End Class

Module C1Extensions
    <Extension>
    Sub M2(c As C1)
        ...
    End Sub
End Module

为了便于访问,扩展方法也被视为他们在其中声明的标准模块的成员-它们没有对其扩展的类型成员的额外访问权限,因为它们的声明上下文超出了他们拥有的访问范围。

扩展方法仅在标准模块方法处于范围内时才可用。 否则,原始类型似乎尚未扩展。 例如:

Imports System.Runtime.CompilerServices

Class C1
End Class

Namespace N1
    Module C1Extensions
        <Extension> _
        Sub M1(c As C1)
            ...
        End Sub
    End Module
End Namespace

Module Test
    Sub Main()
        Dim c As New C1()

        ' Error: c has no member named "M1"
        c.M1()
    End Sub
End Module

当只有该类型的扩展方法可用时,引用类型仍将生成编译时错误。

请务必注意,扩展方法被视为绑定成员的所有上下文中的类型成员,例如强类型 For Each 模式。 例如:

Imports System.Runtime.CompilerServices

Class C1
End Class

Class C1Enumerator
    ReadOnly Property Current() As C1
        Get
            ...
        End Get
    End Property

    Function MoveNext() As Boolean
        ...
    End Function
End Class

Module C1Extensions
    <Extension> _
    Function GetEnumerator(c As C1) As C1Enumerator
        ...
    End Function
End Module

Module Test
    Sub Main()
        Dim c As New C1()

        ' Valid
        For Each o As Object In c
            ...
        Next o
    End Sub
End Module

还可以创建引用扩展方法的委托。 因此,代码:

Delegate Sub D1()

Module Test
    Sub Main()
        Dim s As String = "Hello, World!"
        Dim d As D1

        d = AddressOf s.Print
        d()
    End Sub
End Module

大致等效于:

Delegate Sub D1()

Module Test
    Sub Main()
      Dim s As String = "Hello, World!"
      Dim d As D1

      d = CType([Delegate].CreateDelegate(GetType(D1), s, _
                GetType(StringExtensions).GetMethod("Print")), D1)
      d()
    End Sub
End Module

注意。 Visual Basic 通常会在实例方法调用上插入检查,如果调用该方法的实例为 Nothing,则会导致System.NullReferenceException发生。 对于扩展方法,无法有效地插入此检查,因此扩展方法需要显式检查 Nothing

注意。 将值类型作为参数传递给类型为 ByVal 接口的参数时,将装箱。 这意味着扩展方法的副作用将对结构的副本而不是原始副本进行作。 虽然语言对扩展方法的第一个参数没有限制,但建议扩展方法不用于扩展值类型,或者在扩展值类型时传递 ByRef 第一个参数以确保对原始值产生副作用。

分部方法

分部方法是一种指定签名而不是方法正文的方法。 方法的正文可由另一个具有相同名称和签名的方法声明提供,这很可能是在类型的另一部分声明中。 例如:

a.vb:

' Designer generated code
Public Partial Class MyForm
    Private Partial Sub ValidateControls()
    End Sub

    Public Sub New()
        ' Initialize controls
        ...

        ValidateControls()
    End Sub    
End Class

b.vb:

Public Partial Class MyForm
    Public Sub ValidateControls()
        ' Validation logic goes here
        ...
    End Sub
End Class

在此示例中,类 MyForm 的分部声明声明不带实现的分部方法 ValidateControls 。 分部声明中的构造函数调用分部方法,即使文件中没有提供正文。 然后,另一部分声明 MyForm 提供方法的实现。

无论是否提供了正文,都可以调用分部方法;如果未提供方法正文,则忽略调用。 例如:

Public Class C1
    Private Partial Sub M1()
    End Sub

    Public Sub New()
        ' Since no implementation is supplied, this call will not be made.
        M1()
    End Sub
End Class

同时忽略作为参数传入到分部方法调用的任何表达式,也不会被计算。 (注意。 这意味着分部方法是提供跨两个分部类型定义的行为的一种非常高效的方法,因为分部方法在不使用时没有成本。

分部方法声明必须声明为 Private 子例程,其正文中没有语句。 分部方法本身不能实现接口方法,尽管提供其正文的方法可以。

只有一种方法可以向分部方法提供正文。 向分部方法提供正文的方法必须具有与分部方法相同的签名、对任何类型参数相同的约束、相同的声明修饰符以及相同的参数和类型参数名称。 分部方法的属性和提供其正文的方法将合并,方法参数上的任何属性也一样。 同样,合并方法句柄的事件列表。 例如:

Class C1
    Event E1()
    Event E2()

    Private Partial Sub S() Handles Me.E1
    End Sub

    ' Handles both E1 and E2
    Private Sub S() Handles Me.E2
        ...
    End Sub
End Class

构造函数

构造函数 是允许控制初始化的特殊方法。 它们将在程序开始或创建类型的实例后运行。 与其他成员不同,构造函数不会继承,也不会在类型的声明空间中引入名称。 构造函数只能由对象创建表达式或 .NET Framework 调用;它们可能永远不会直接调用。

注意。 构造函数对子例程具有的行放置具有相同的限制。 开头语句、end 语句和块必须全部显示在逻辑行的开头。

ConstructorMemberDeclaration
    : Attributes? ConstructorModifier* 'Sub' 'New'
      ( OpenParenthesis ParameterList? CloseParenthesis )? LineTerminator
      Block?
      'End' 'Sub' StatementTerminator
    ;

ConstructorModifier
    : AccessModifier
    | 'Shared'
    ;

实例构造函数

实例构造函数 初始化类型的实例,并在创建实例时由 .NET Framework 运行。 构造函数的参数列表受方法参数列表相同的规则约束。 实例构造函数可能会重载。

引用类型中的所有构造函数都必须调用另一个构造函数。 如果调用是显式的,则它必须是构造函数方法正文中的第一个语句。 该语句可以调用另一个类型的实例构造函数(例如, Me.New(...)MyClass.New(...) )或者,如果不是结构,则它可以调用该类型的基类型的实例构造函数,例如 MyBase.New(...)。 构造函数调用自身无效。 如果构造函数省略对另一个构造函数的调用, MyBase.New() 则为隐式。 如果没有无参数基类型构造函数,则会发生编译时错误。 由于Me在调用基类构造函数之后才被视为构造函数,因此构造函数调用语句的参数不能引用或MyClass隐式或MyBase显式引用Me

当构造函数的第一个语句为表单 MyBase.New(...)时,构造函数将隐式执行由类型中声明的实例变量初始值设定项指定的初始化。 这对应于调用直接基类型构造函数后立即执行的一系列赋值。 此类排序可确保在执行任何有权访问实例的语句之前,所有基实例变量都由其变量初始值设定项初始化。 例如:

Class A
    Protected x As Integer = 1
End Class

Class B
    Inherits A

    Private y As Integer = x

    Public Sub New()
        Console.WriteLine("x = " & x & ", y = " & y)
    End Sub
End Class

用于创建实例BNew B(),将生成以下输出:

x = 1, y = 1

y 1这是因为在调用基类构造函数后执行变量初始值设定项。 变量初始值设定项按类型声明中显示的文本顺序执行。

当类型仅 Private 声明构造函数时,其他类型通常不可能派生自类型或创建类型的实例;唯一的异常是嵌套在类型中的类型。 Private 构造函数通常用于仅 Shared 包含成员的类型。

如果类型不包含实例构造函数声明,则会自动提供默认构造函数。 默认构造函数只是调用直接基类型的无参数构造函数。 如果直接基类型没有可访问的无参数构造函数,则会发生编译时错误。 默认构造函数的声明访问类型是 Public 除非类型为 MustInherit,在这种情况下,默认构造函数为 Protected

注意。 类型的默认构造函数的默认访问MustInherit是因为ProtectedMustInherit无法直接创建类。 因此,没有建立默认构造函数 Public的点。

在以下示例中,提供了默认构造函数,因为该类不包含构造函数声明:

Class Message
    Dim sender As Object
    Dim text As String
End Class

因此,该示例与以下内容完全等效:

Class Message
    Dim sender As Object
    Dim text As String

    Sub New()
    End Sub
End Class

在调用基构造函数后,向设计器生成的类 Microsoft.VisualBasic.CompilerServices.DesignerGeneratedAttribute 发出的默认构造函数将调用方法 Sub InitializeComponent()(如果存在)。 (注意。 这允许设计器生成的文件(如 WinForms 设计器创建的文件)省略设计器文件中的构造函数。这样,程序员就可以自行指定它(如果如此选择)。

共享构造函数

共享构造函数 初始化类型的共享变量;它们在程序开始执行之后运行,但在对类型成员的任何引用之前运行。 共享构造函数指定 Shared 修饰符,除非它是在标准模块中,在这种情况下,修饰符是隐含的 Shared

与实例构造函数不同,共享构造函数具有隐式公共访问、没有参数,并且不能调用其他构造函数。 在共享构造函数中的第一个语句之前,共享构造函数隐式执行由类型中声明的共享变量初始值设定项指定的初始化。 这对应于在进入构造函数后立即执行的一系列赋值。 变量初始值设定项按类型声明中显示的文本顺序执行。

以下示例演示一个 Employee 类,该类具有初始化共享变量的共享构造函数:

Imports System.Data

Class Employee
    Private Shared ds As DataSet

    Shared Sub New()
        ds = New DataSet()
    End Sub

    Public Name As String
    Public Salary As Decimal
End Class

每个封闭泛型类型都存在单独的共享构造函数。 由于共享构造函数为每个已关闭类型完全执行一次,因此对无法通过约束在编译时检查的类型参数强制执行运行时检查是一个方便的位置。 例如,以下类型使用共享构造函数强制类型参数为 IntegerDouble

Class EnumHolder(Of T)
    Shared Sub New() 
        If Not GetType(T).IsEnum() Then
            Throw New ArgumentException("T must be an enumerated type.")
        End If
    End Sub
End Class

完全当运行共享构造函数主要依赖于实现时,如果显式定义了共享构造函数,则会提供若干保证:

  • 共享构造函数在首次访问类型的任何静态字段之前运行。

  • 共享构造函数在首次调用类型的任何静态方法之前运行。

  • 共享构造函数在首次调用类型的任何构造函数之前运行。

上述保证不适用于为共享初始值设定项隐式创建共享构造函数的情况。 以下示例的输出不确定,因为加载的确切顺序,因此未定义共享构造函数执行的确切顺序:

Module Test
    Sub Main()
        A.F()
        B.F()
    End Sub
End Module

Class A
    Shared Sub New()
        Console.WriteLine("Init A")
    End Sub

    Public Shared Sub F()
        Console.WriteLine("A.F")
    End Sub
End Class

Class B
    Shared Sub New()
        Console.WriteLine("Init B")
    End Sub

    Public Shared Sub F()
        Console.WriteLine("B.F")
    End Sub
End Class

输出可以是以下任一项:

Init A
A.F
Init B
B.F

Init B
Init A
A.F
B.F

相比之下,以下示例生成可预测的输出。 请注意,Shared即使类派生自该类,该类AB的构造函数也永远不会执行:

Module Test
    Sub Main()
        B.G()
    End Sub
End Module

Class A
    Shared Sub New()
        Console.WriteLine("Init A")
    End Sub
End Class

Class B
    Inherits A

    Shared Sub New()
        Console.WriteLine("Init B")
    End Sub

    Public Shared Sub G()
        Console.WriteLine("B.G")
    End Sub
End Class

输出为:

Init B
B.G

还可以构造循环依赖项,允许 Shared 以默认值状态观察变量初始值设定项的变量,如以下示例所示:

Class A
    Public Shared X As Integer = B.Y + 1
End Class

Class B
    Public Shared Y As Integer = A.X + 1

    Shared Sub Main()
        Console.WriteLine("X = " & A.X & ", Y = " & B.Y)
    End Sub
End Class

这会生成输出:

X = 1, Y = 2

若要执行该方法 Main ,系统首先加载类 BSharedB的构造函数继续计算初始值,该值Y以递归方式导致类A加载,因为引用的值A.X。 类SharedA的构造函数反过来会继续计算其初始值X,这样做会提取默认值Y,默认值为零。 A.X 因此,初始化为 1. 然后加载 A 过程完成,返回到初始值的 Y计算,其结果将变为 2此结果。

Main如果该方法位于类A中,则本示例将生成以下输出:

X = 2, Y = 1

避免变量初始值设定项中的 Shared 循环引用,因为通常无法确定加载包含此类引用的类的顺序。

事件

事件用于通知特定事件的代码。 事件声明由标识符、委托类型或参数列表和可选 Implements 子句组成。

EventMemberDeclaration
    : RegularEventMemberDeclaration
    | CustomEventMemberDeclaration
    ;

RegularEventMemberDeclaration
    : Attributes? EventModifiers* 'Event'
      Identifier ParametersOrType ImplementsClause? StatementTerminator
    ;

InterfaceEventMemberDeclaration
    : Attributes? InterfaceEventModifiers* 'Event'
      Identifier ParametersOrType StatementTerminator
    ;

ParametersOrType
    : ( OpenParenthesis ParameterList? CloseParenthesis )?
    | 'As' NonArrayTypeName
    ;

EventModifiers
    : AccessModifier
    | 'Shadows'
    | 'Shared'
    ;

InterfaceEventModifiers
    : 'Shadows'
    ;

如果指定了委托类型,则委托类型可能没有返回类型。 如果指定了参数列表,则它可能不包含 OptionalParamArray 参数。 参数类型和/或委托类型的辅助功能域必须与事件本身的辅助功能域或超集相同。 可以通过指定 Shared 修饰符来共享事件。

除了添加到类型声明空间的成员名称之外,事件声明还隐式声明其他几个成员。 给定命名 X的事件后,以下成员将添加到声明空间:

  • 如果声明的形式是方法声明,则会引入一个名为 XEventHandler 嵌套的委托类。 嵌套委托类与方法声明匹配,并且具有与事件相同的辅助功能。 参数列表中的属性适用于委托类的参数。

  • Private类型化为委托的实例变量,命名XEvent为 。

  • 两个命名 add_X 的方法, remove_X 不能调用、重写或重载。

如果类型尝试声明与上述名称之一匹配的名称,则会导致编译时错误,并且出于名称绑定的目的,将忽略隐式 add_Xremove_X 声明。 无法替代或重载任何引入的成员,尽管可以在派生类型中隐藏它们。 例如,类声明

Class Raiser
    Public Event Constructed(i As Integer)
End Class

等效于以下声明

Class Raiser
    Public Delegate Sub ConstructedEventHandler(i As Integer)

    Protected ConstructedEvent As ConstructedEventHandler

    Public Sub add_Constructed(d As ConstructedEventHandler)
        ConstructedEvent = _
            CType( _
                [Delegate].Combine(ConstructedEvent, d), _
                    Raiser.ConstructedEventHandler)
    End Sub

    Public Sub remove_Constructed(d As ConstructedEventHandler)
        ConstructedEvent = _
            CType( _
                [Delegate].Remove(ConstructedEvent, d), _
                    Raiser.ConstructedEventHandler)
    End Sub
End Class

在不指定委托类型的情况下声明事件是最简单且最紧凑语法,但对于每个事件声明新的委托类型有缺点。 例如,在以下示例中,将创建三个隐藏委托类型,即使所有三个事件具有相同的参数列表:

Public Class Button
    Public Event Click(sender As Object, e As EventArgs)
    Public Event DoubleClick(sender As Object, e As EventArgs)
    Public Event RightClick(sender As Object, e As EventArgs)
End Class

在以下示例中,事件只使用相同的委托: EventHandler

Public Delegate Sub EventHandler(sender As Object, e As EventArgs)

Public Class Button
    Public Event Click As EventHandler
    Public Event DoubleClick As EventHandler
    Public Event RightClick As EventHandler
End Class

事件可以通过以下两种方式之一进行处理:静态或动态处理。 静态处理事件更简单,只需要变量 WithEventsHandles 子句。 在以下示例中,类Form1静态处理对象Button的事件Click

Public Class Form1
    Public WithEvents Button1 As New Button()

    Public Sub Button1_Click(sender As Object, e As EventArgs) _
           Handles Button1.Click
        Console.WriteLine("Button1 was clicked!")
    End Sub
End Class

动态处理事件更为复杂,因为事件必须显式连接并在代码中断开连接。 该语句 AddHandler 为事件添加处理程序,该语句 RemoveHandler 删除事件的处理程序。 下一个示例演示了一个类,该类 Form1 添加 Button1_ClickButton1's Click 事件的事件处理程序:

Public Class Form1
    Public Sub New()
        ' Add Button1_Click as an event handler for Button1's Click event.
        AddHandler Button1.Click, AddressOf Button1_Click
    End Sub 

    Private Button1 As Button = New Button()

    Sub Button1_Click(sender As Object, e As EventArgs)
        Console.WriteLine("Button1 was clicked!")
    End Sub

    Public Sub Disconnect()
        RemoveHandler Button1.Click, AddressOf Button1_Click
    End Sub 
End Class

在方法 Disconnect中,将删除事件处理程序。

自定义事件

如上一部分所述,事件声明隐式定义字段、 add_ 方法和 remove_ 用于跟踪事件处理程序的方法。 但是,在某些情况下,可能需要提供用于跟踪事件处理程序的自定义代码。 例如,如果类定义了只处理过几个事件的 40 个事件,则使用哈希表而不是 40 个字段来跟踪每个事件的处理程序可能更高效。 自定义事件 允许 add_X 显式定义和 remove_X 方法,从而为事件处理程序启用自定义存储。

自定义事件声明的方式与声明委托类型的事件的方式相同,但关键字必须位于关键字 Custom 前面 Event 。 自定义事件声明包含三个声明:声明 AddHandlerRemoveHandler 声明和 RaiseEvent 声明。 这些声明都不能有任何修饰符,尽管它们可以具有属性。

CustomEventMemberDeclaration
    : Attributes? EventModifiers* 'Custom' 'Event'
      Identifier 'As' TypeName ImplementsClause? StatementTerminator
      EventAccessorDeclaration+
      'End' 'Event' StatementTerminator
    ;

EventAccessorDeclaration
    : AddHandlerDeclaration
    | RemoveHandlerDeclaration
    | RaiseEventDeclaration
    ;

AddHandlerDeclaration
    : Attributes? 'AddHandler'
      OpenParenthesis ParameterList CloseParenthesis LineTerminator
      Block?
      'End' 'AddHandler' StatementTerminator
    ;

RemoveHandlerDeclaration
    : Attributes? 'RemoveHandler'
      OpenParenthesis ParameterList CloseParenthesis LineTerminator
      Block?
      'End' 'RemoveHandler' StatementTerminator
    ;

RaiseEventDeclaration
    : Attributes? 'RaiseEvent'
      OpenParenthesis ParameterList CloseParenthesis LineTerminator
      Block?
      'End' 'RaiseEvent' StatementTerminator
    ;

例如:

Class Test
    Private Handlers As EventHandler

    Public Custom Event TestEvent As EventHandler
        AddHandler(value As EventHandler)
            Handlers = CType([Delegate].Combine(Handlers, value), _
                EventHandler)
        End AddHandler

        RemoveHandler(value as EventHandler)
            Handlers = CType([Delegate].Remove(Handlers, value), _
                EventHandler)
        End RemoveHandler

        RaiseEvent(sender As Object, e As EventArgs)
            Dim TempHandlers As EventHandler = Handlers

            If TempHandlers IsNot Nothing Then
                TempHandlers(sender, e)
            End If
        End RaiseEvent
    End Event
End Class

声明AddHandlerRemoveHandler采用一个ByVal参数,该参数必须是事件的委托类型。 AddHandler执行或RemoveHandler语句(或Handles子句自动处理事件)时,将调用相应的声明。 声明 RaiseEvent 采用与事件委托相同的参数,并在执行语句时 RaiseEvent 调用。 必须提供所有声明,并被视为子例程。

请注意 AddHandlerRemoveHandler 声明 RaiseEvent 对子例程具有的行放置具有相同的限制。 开头语句、end 语句和块必须全部显示在逻辑行的开头。

除了添加到类型声明空间的成员名称之外,自定义事件声明还隐式声明其他几个成员。 给定命名 X的事件后,以下成员将添加到声明空间:

  • 一个与声明对应的AddHandler命名add_X方法。

  • 一个与声明对应的RemoveHandler命名remove_X方法。

  • 一个与声明对应的RaiseEvent命名fire_X方法。

如果类型尝试声明与上述名称之一匹配的名称,则会导致编译时错误,并且出于名称绑定的目的,将忽略隐式声明。 无法替代或重载任何引入的成员,尽管可以在派生类型中隐藏它们。

注意。 Custom 不是保留字。

WinRT 程序集中的自定义事件

从 Microsoft Visual Basic 11.0 开始,使用此类文件中编译 /target:winmdobj或声明的接口中声明的事件,然后在其他位置实现的事件会略有不同。

  • 用于生成 winmd 的外部工具通常只允许某些委托类型,或者System.EventHandler(Of T)System.TypedEventHandle(Of T, U),并且将禁止其他委托类型。

  • XEvent 字段的类型 System.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenTable(Of T)T 委托类型。

  • AddHandler 访问器返回 a,RemoveHandler System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken访问器采用同一类型的单个参数。

下面是此类自定义事件的示例。

Imports System.Runtime.InteropServices.WindowsRuntime

Public NotInheritable Class ClassInWinMD
    Private XEvent As EventRegistrationTokenTable(Of EventHandler(Of Integer))

    Public Custom Event X As EventHandler(Of Integer)
        AddHandler(handler As EventHandler(Of Integer))
            Return EventRegistrationTokenTable(Of EventHandler(Of Integer)).
                   GetOrCreateEventRegistrationTokenTable(XEvent).
                   AddEventHandler(handler)
        End AddHandler

        RemoveHandler(token As EventRegistrationToken)
            EventRegistrationTokenTable(Of EventHandler(Of Integer)).
                GetOrCreateEventRegistrationTokenTable(XEvent).
                RemoveEventHandler(token)
        End RemoveHandler

        RaiseEvent(sender As Object, i As Integer)
            Dim table = EventRegistrationTokenTable(Of EventHandler(Of Integer)).
                GetOrCreateEventRegistrationTokenTable(XEvent).
                InvocationList
            If table IsNot Nothing Then table(sender, i)
        End RaiseEvent
    End Event
End Class

常量

常量是类型成员的常量值。

ConstantMemberDeclaration
    : Attributes? ConstantModifier* 'Const' ConstantDeclarators StatementTerminator
    ;

ConstantModifier
    : AccessModifier
    | 'Shadows'
    ;

ConstantDeclarators
    : ConstantDeclarator ( Comma ConstantDeclarator )*
    ;

ConstantDeclarator
    : Identifier ( 'As' TypeName )? Equals ConstantExpression StatementTerminator
    ;

常量是隐式共享的。 如果声明包含子 As 句,则子句指定声明引入的成员的类型。 如果省略类型,则会推断常量的类型。 常量的类型只能是基元类型或 Object。 如果常量的类型化且 Object 没有类型字符,则常量的实际类型将是常量表达式的类型。 否则,常量的类型是常量的类型字符的类型。

以下示例显示了一个名为 Constants 具有两个公共常量的类:

Class Constants
    Public Const A As Integer = 1
    Public Const B As Integer = A + 1
End Class

可以通过类访问常量,如以下示例所示,该类输出 Constants.A 值和 Constants.B

Module Test
    Sub Main()
        Console.WriteLine(Constants.A & ", " & Constants.B)
    End Sub 
End Module

声明多个常量的常量声明等效于单个常量的多个声明。 以下示例在一个声明语句中声明三个常量。

Class A
    Protected Const x As Integer = 1, y As Long = 2, z As Short = 3
End Class

此声明等效于以下各项:

Class A
    Protected Const x As Integer = 1
    Protected Const y As Long = 2
    Protected Const z As Short = 3
End Class

常量类型的辅助功能域必须与常量本身的辅助功能域或超集相同。 常量表达式必须生成常量类型的值或隐式转换为常量类型的值。 常量表达式可能不是循环的;也就是说,一个常量本身可能未定义。

编译器按适当的顺序自动计算常量声明。 在下面的示例中,编译器首先计算Y值,最后ZX生成值 10、11 和 12。

Class A
    Public Const X As Integer = B.Z + 1
    Public Const Y As Integer = 10
End Class

Class B
    Public Const Z As Integer = A.Y + 1
End Class

如果需要常量值的符号名称,但常量声明中不允许使用该值的类型,或者当常量表达式在编译时无法计算该值时,可以改用只读变量。

实例和共享变量

实例或共享变量是可以存储信息的类型的成员。

VariableMemberDeclaration
    : Attributes? VariableModifier+ VariableDeclarators StatementTerminator
    ;

VariableModifier
    : AccessModifier
    | 'Shadows'
    | 'Shared'
    | 'ReadOnly'
    | 'WithEvents'
    | 'Dim'
    ;

VariableDeclarators
    : VariableDeclarator ( Comma VariableDeclarator )*
    ;

VariableDeclarator
    : VariableIdentifiers 'As' ObjectCreationExpression
    | VariableIdentifiers ( 'As' TypeName )? ( Equals Expression )?
    ;

VariableIdentifiers
    : VariableIdentifier ( Comma VariableIdentifier )*
    ;

VariableIdentifier
    : Identifier IdentifierModifiers
    ;

Dim如果未指定修饰符,则必须指定修饰符,但可以省略该修饰符。 单个变量声明可以包含多个变量声明符;每个变量声明符都会引入一个新实例或共享成员。

如果指定初始值设定项,则变量声明符只能声明一个实例或共享变量:

Class Test
    Dim a, b, c, d As Integer = 10  ' Invalid: multiple initialization
End Class

此限制不适用于对象初始值设定项:

Class Test
    Dim a, b, c, d As New Collection() ' OK
End Class

使用 Shared 修饰符声明的变量是 共享变量。 无论创建的类型的实例数如何,共享变量都确切地标识一个存储位置。 当程序开始执行时,共享变量就存在,当程序终止时将停止存在。

共享变量仅在特定封闭泛型类型的实例之间共享。 例如,程序:

Class C(Of V) 
    Shared InstanceCount As Integer = 0

    Public Sub New()  
        InstanceCount += 1 
    End Sub

    Public Shared ReadOnly Property Count() As Integer 
        Get
            Return InstanceCount
        End Get
    End Property
End Class

Class Application 
    Shared Sub Main() 
        Dim x1 As New C(Of Integer)()
        Console.WriteLine(C(Of Integer).Count)

        Dim x2 As New C(Of Double)() 
        Console.WriteLine(C(Of Integer).Count)

        Dim x3 As New C(Of Integer)() 
        Console.WriteLine(C(Of Integer).Count)
    End Sub
End Class

输出:

1
1
2

在没有修饰符的情况下声明的 Shared 变量称为 实例变量。 类的每个实例都包含该类的所有实例变量的单独副本。 当创建该类型的新实例时,引用类型的实例变量就存在,并且当没有对该实例的引用和执行 Finalize 该方法时,该实例将停止存在。 值类型的实例变量的生存期与它所属的变量完全相同。 换句话说,当值类型的变量存在或不再存在时,值类型的实例变量也是如此。

如果声明符包含子 As 句,则子句指定声明引入的成员的类型。 如果省略了类型并使用了严格的语义,则会发生编译时错误。 否则,成员的类型是 Object 隐式的或成员的类型字符的类型。

注意。 语法中没有歧义:如果声明符省略类型,它将始终使用以下声明符的类型。

实例或共享变量的类型或数组元素类型的辅助功能域必须与实例或共享变量本身的辅助功能域的超集相同或超集。

以下示例显示了一个 类,该类具有名为 /> 的内部实例变量,以及

Class Color
    Friend redPart As Short
    Friend bluePart As Short
    Friend greenPart As Short

    Public Sub New(red As Short, blue As Short, green As Short)
        redPart = red
        bluePart = blue
        greenPart = green
    End Sub
End Class

Read-Only 变量

当实例或共享变量声明包含 ReadOnly 修饰符时,对声明引入的变量的赋值可能仅作为声明的一部分或在同一类的构造函数中发生。 具体而言,仅在以下情况下才允许对只读实例或共享变量的赋值:

  • 在引入实例或共享变量的变量声明中(通过在声明中包括变量初始值设定项)。

  • 对于实例变量,在包含变量声明的类的实例构造函数中。 实例变量只能以不限定的方式或通过 MeMyClass访问。

  • 对于共享变量,在包含共享变量声明的类的共享构造函数中。

当需要常量值的符号名称时,共享只读变量非常有用,但当常量声明中不允许该值的类型,或者当常量表达式在编译时无法计算该值时。

第一个此类应用程序的示例如下,其中声明 ReadOnly 了颜色共享变量以防止其他程序更改它们:

Class Color
    Friend redPart As Short
    Friend bluePart As Short
    Friend greenPart As Short

    Public Sub New(red As Short, blue As Short, green As Short)
        redPart = red
        bluePart = blue
        greenPart = green
    End Sub 

    Public Shared ReadOnly Red As Color = New Color(&HFF, 0, 0)
    Public Shared ReadOnly Blue As Color = New Color(0, &HFF, 0)
    Public Shared ReadOnly Green As Color = New Color(0, 0, &HFF)
    Public Shared ReadOnly White As Color = New Color(&HFF, &HFF, &HFF)
End Class

常量和只读共享变量具有不同的语义。 当表达式引用常量时,在编译时获取常量的值,但当表达式引用只读共享变量时,在运行时才会获取共享变量的值。 请考虑以下应用程序,该应用程序由两个单独的程序组成。

file1.vb:

Namespace Program1
    Public Class Utils
        Public Shared ReadOnly X As Integer = 1
    End Class
End Namespace

file2.vb:

Namespace Program2
    Module Test
        Sub Main()
            Console.WriteLine(Program1.Utils.X)
        End Sub
    End Module
End Namespace

命名空间 Program1Program2 表示单独编译的两个程序。 由于变量 Program1.Utils.X 声明为 Shared ReadOnly变量,因此语句输出 Console.WriteLine 的值在编译时未知,而是在运行时获取。 因此,如果更改 X 值并 Program1 重新编译,则即使未重新编译,该 Console.WriteLine 语句也会输出新值 Program2 。 但是,如果X一直是常量,则在编译时Program2获取的值X,并且在重新编译之前Program2,这些更改Program1将不受影响。

WithEvents 变量

类型可以通过声明使用 WithEvents 修饰符引发事件的实例或共享变量来声明它处理由其中一个实例或共享变量引发的某些事件集。 例如:

Class Raiser
    Public Event E1()

    Public Sub Raise()
        RaiseEvent E1
    End Sub
End Class

Module Test
    Private WithEvents x As Raiser

    Private Sub E1Handler() Handles x.E1
        Console.WriteLine("Raised")
    End Sub

    Public Sub Main()
        x = New Raiser()
    End Sub
End Module

在此示例中,该方法E1Handler处理由存储在实例变量x中的类型的Raiser实例引发的事件E1

修饰 WithEvents 符使变量重命名为前导下划线,并替换为执行事件挂钩的相同名称的属性。 例如,如果变量的名称 F为,则会将其 _F 重命名,并隐式声明属性 F 。 如果变量的新名称和另一个声明之间存在冲突,则会报告编译时错误。 应用于变量的任何属性将传递到重命名的变量。

声明 WithEvents 创建的隐式属性负责挂钩和取消调用相关的事件处理程序。 将值赋给变量时,该属性首先调用 remove 变量中当前实例上的事件的方法(如果存在的话)。。 接下来进行赋值,属性 add 调用变量中新实例上的事件的方法(挂接新的事件处理程序)。 以下代码等效于上述标准模块 Test的代码:

Module Test
    Private _x As Raiser

    Public Property x() As Raiser
        Get
            Return _x
        End Get

        Set (Value As Raiser)
            ' Unhook any existing handlers.
            If _x IsNot Nothing Then
                RemoveHandler _x.E1, AddressOf E1Handler
            End If

            ' Change value.
            _x = Value

            ' Hook-up new handlers.
            If _x IsNot Nothing Then
                AddHandler _x.E1, AddressOf E1Handler
            End If
        End Set
    End Property

    Sub E1Handler()
        Console.WriteLine("Raised")
    End Sub

    Sub Main()
        x = New Raiser()
    End Sub
End Module

声明实例或共享变量无效,就像将变量类型化为结构一样 WithEvents 。 此外,WithEvents不能在结构中指定,并且WithEventsReadOnly不能组合在一起。

变量初始值设定项

结构中的类和实例变量声明(但不是共享变量声明)中的实例和共享变量声明可能包括变量初始值设定项。 对于 Shared 变量,变量初始值设定项对应于在程序开始之后执行,但在首次引用变量之前 Shared 执行的赋值语句。 对于实例变量,变量初始值设定项对应于创建类实例时执行的赋值语句。 结构不能具有实例变量初始值设定项,因为它们的无参数构造函数无法修改。

请看下面的示例:

Class Test
    Public Shared x As Double = Math.Sqrt(2.0)
    Public i As Integer = 100
    Public s As String = "Hello"
End Class

Module TestModule
    Sub Main()
        Dim a As New Test()

        Console.WriteLine("x = " & Test.x & ", i = " & a.i & ", s = " & a.s)
    End Sub
End Module

此示例生成以下输出:

x = 1.4142135623731, i = 100, s = Hello

加载类时要x发生的赋值,并在创建类的新实例时执行作业和s发生作业i

将变量初始值设定项视为在类型构造函数块中自动插入的赋值语句非常有用。 以下示例包含多个实例变量初始值设定项。

Class A
    Private x As Integer = 1
    Private y As Integer = -1
    Private count As Integer

    Public Sub New()
        count = 0
    End Sub

    Public Sub New(n As Integer)
        count = n
    End Sub
End Class

Class B
    Inherits A

    Private sqrt2 As Double = Math.Sqrt(2.0)
    Private items As ArrayList = New ArrayList(100)
    Private max As Integer

    Public Sub New()
        Me.New(100)
        items.Add("default")
    End Sub

    Public Sub New(n As Integer)
        MyBase.New(n - 1)
        max = n
    End Sub
End Class

该示例对应于下面所示的代码,其中每个注释都指示自动插入的语句。

Class A
    Private x, y, count As Integer

    Public Sub New()
        MyBase.New ' Invoke object() constructor.
        x = 1 ' This is a variable initializer.
        y = -1 ' This is a variable initializer.
        count = 0
    End Sub

    Public Sub New(n As Integer)
        MyBase.New ' Invoke object() constructor. 
        x = 1 ' This is a variable initializer.
        y = - 1 ' This is a variable initializer.
        count = n
    End Sub
End Class

Class B
    Inherits A

    Private sqrt2 As Double
    Private items As ArrayList
    Private max As Integer

    Public Sub New()
        Me.New(100) 
        items.Add("default")
    End Sub

    Public Sub New(n As Integer)
        MyBase.New(n - 1) 
        sqrt2 = Math.Sqrt(2.0) ' This is a variable initializer.
        items = New ArrayList(100) ' This is a variable initializer.
        max = n
    End Sub
End Class

在执行任何变量初始值设定项之前,所有变量都初始化为其类型的默认值。 例如:

Class Test
    Public Shared b As Boolean
    Public i As Integer
End Class

Module TestModule
    Sub Main()
        Dim t As New Test()
        Console.WriteLine("b = " & Test.b & ", i = " & t.i)
    End Sub
End Module

由于 b 在加载类时自动初始化为其默认值,并在 i 创建类实例时自动初始化为其默认值,因此上述代码将生成以下输出:

b = False, i = 0

每个变量初始值设定项必须生成变量的类型或隐式转换为变量类型的值。 变量初始值设定项可以是循环的,也可以引用将在变量后初始化的变量,在这种情况下,所引用变量的值是其默认值,以用于初始值设定项。 此类初始值设定项具有可疑值。

有三种形式的变量初始值设定项:常规初始值设定项、数组大小初始值设定项和对象初始值设定项。 前两个窗体出现在类型名称后面的等号之后,后两种形式是声明本身的一部分。 任何特定声明只能使用一种初始值设定项形式。

常规初始值设定项

正则初始值设定项是隐式转换为变量类型的表达式。 它出现在类型名称后面的等号之后,必须归类为值。 例如:

Module Test
    Dim x As Integer = 10
    Dim y As Integer = 20

    Sub Main()
        Console.WriteLine("x = " & x & ", y = " & y)
    End Sub
End Module

此程序生成输出:

x = 10, y = 20

如果变量声明具有常规初始值设定项,则一次只能声明一个变量。 例如:

Module Test
    Sub Main()
        ' OK, only one variable declared at a time.
        Dim x As Integer = 10, y As Integer = 20

        ' Error: Can't initialize multiple variables at once.
        Dim a, b As Integer = 10
    End Sub
End Module

对象初始值设定项

使用对象创建表达式指定 对象初始值设定 项来代替类型名称。 对象初始值设定项等效于将对象创建表达式的结果分配给变量的正则初始值设定项。 So

Module TestModule
    Sub Main()
        Dim x As New Test(10)
    End Sub
End Module

等效于

Module TestModule
    Sub Main()
        Dim x As Test = New Test(10)
    End Sub
End Module

对象初始值设定项中的括号始终解释为构造函数的参数列表,永远不会解释为数组类型修饰符。 具有对象初始值设定项的变量名称不能具有数组类型修饰符或可为 null 的类型修饰符。

Array-Size 初始值设定项

数组大小初始值设定项是变量名称的修饰符,它提供表达式表示的一组维度上限。

ArraySizeInitializationModifier
    : OpenParenthesis BoundList CloseParenthesis ArrayTypeModifiers?
    ;

BoundList
    : Bound ( Comma Bound )*
    ;

Bound
    : Expression
    | '0' 'To' Expression
    ;

上限表达式必须分类为值,并且必须隐式转换为 Integer。 上限集等效于具有给定上限的数组创建表达式的变量初始值设定项。 从数组大小初始值设定项推断数组类型的维度数。 So

Module Test
    Sub Main()
        Dim x(5, 10) As Integer
    End Sub
End Module

等效于

Module Test
    Sub Main()
        Dim x As Integer(,) = New Integer(5, 10) {}
    End Sub
End Module

所有上限必须等于或大于 -1,并且所有维度都必须指定上限。 如果正在初始化的数组的元素类型本身是数组类型,则数组类型修饰符将转到数组大小初始值设定项的右侧。 例如:

Module Test
    Sub Main()
        Dim x(5,10)(,,) As Integer
    End Sub
End Module

声明一个局部变量 x ,其类型为三维数组的二维数组 Integer,初始化为具有 0..5 第一个维度和第 0..10 二维边界的数组。 无法使用数组大小初始值设定项来初始化其类型为数组数组的变量的元素。

具有数组大小初始值设定项的变量声明在其类型或常规初始值设定项上不能有数组类型修饰符。

System.MarshalByRefObject 类

派生自类的类 System.MarshalByRefObject 使用代理(即引用)而不是通过复制(即值)跨上下文边界封送。 这意味着此类的实例可能不是真正的实例,而是封送变量访问和方法调用跨上下文边界的存根。

因此,无法创建对此类上定义的变量的存储位置的引用。 这意味着无法将类型化为派生自 System.MarshalByRefObject 的类的变量传递给引用参数,并且可能无法访问类型为值类型的变量的方法和变量。 相反,Visual Basic 会将此类上定义的变量视为属性(因为这些限制在属性上相同)。

此规则有一个例外:隐式或显式限定的成员 Me 不受上述限制的限制,因为 Me 始终保证为实际对象,而不是代理。

属性

属性 是变量的自然扩展;两者都是具有关联类型的命名成员,用于访问变量和属性的语法是相同的。 但是,与变量不同,属性不表示存储位置。 相反,属性具有 访问器,这些访问器指定要执行的语句,以便读取或写入其值。

使用属性声明定义属性。 属性声明的第一部分类似于字段声明。 第二部分包括 Get 访问器和/或 Set 访问器。

PropertyMemberDeclaration
    : RegularPropertyMemberDeclaration
    | MustOverridePropertyMemberDeclaration
    | AutoPropertyMemberDeclaration
    ;

PropertySignature
    : 'Property'
      Identifier ( OpenParenthesis ParameterList? CloseParenthesis )?
      ( 'As' Attributes? TypeName )?
    ;

RegularPropertyMemberDeclaration
    : Attributes? PropertyModifier* PropertySignature
      ImplementsClause? LineTerminator
      PropertyAccessorDeclaration+
      'End' 'Property' StatementTerminator
    ;

MustOverridePropertyMemberDeclaration
    : Attributes? MustOverridePropertyModifier+ PropertySignature
      ImplementsClause? StatementTerminator
    ;

AutoPropertyMemberDeclaration
    : Attributes? AutoPropertyModifier* 'Property' Identifier
      ( OpenParenthesis ParameterList? CloseParenthesis )?
      ( 'As' Attributes? TypeName )? ( Equals Expression )?
      ImplementsClause? LineTerminator
    | Attributes? AutoPropertyModifier* 'Property' Identifier
      ( OpenParenthesis ParameterList? CloseParenthesis )?
      'As' Attributes? 'New'
      ( NonArrayTypeName ( OpenParenthesis ArgumentList? CloseParenthesis )? )?
      ObjectCreationExpressionInitializer?
      ImplementsClause? LineTerminator
    ;

InterfacePropertyMemberDeclaration
    : Attributes? InterfacePropertyModifier* PropertySignature StatementTerminator
    ;

AutoPropertyModifier
    : AccessModifier
    | 'Shadows'
    | 'Shared'
    | 'Overridable'
    | 'NotOverridable'
    | 'Overrides'
    | 'Overloads'
    ;

PropertyModifier
    : AutoPropertyModifier
    | 'Default'
    | 'ReadOnly'
    | 'WriteOnly'
    | 'Iterator'
    ;

MustOverridePropertyModifier
    : PropertyModifier
    | 'MustOverride'
    ;

InterfacePropertyModifier
    : 'Shadows'
    | 'Overloads'
    | 'Default'
    | 'ReadOnly'
    | 'WriteOnly'
    ;

PropertyAccessorDeclaration
    : PropertyGetDeclaration
    | PropertySetDeclaration
    ;

在下面的示例中,类 Button 定义属性 Caption

Public Class Button
    Private captionValue As String

    Public Property Caption() As String
        Get
            Return captionValue
        End Get

        Set (Value As String)
            captionValue = value
            Repaint()
        End Set
    End Property

    ...
End Class

根据 Button 上面的类,下面是使用该 Caption 属性的示例:

Dim okButton As Button = New Button()

okButton.Caption = "OK" ' Invokes Set accessor.
Dim s As String = okButton.Caption ' Invokes Get accessor.

在这里,通过 Set 向属性分配值来调用访问器,并且 Get 通过引用表达式中的属性来调用访问器。

如果未为属性指定任何类型,并且正在使用严格的语义,则会发生编译时错误;否则,该属性的类型是 Object 隐式的或属性的类型字符的类型。 属性声明可以包含一个 Get 访问器,该访问器检索属性的值、存储 Set 属性的值的访问器或两者的值。 由于属性隐式声明方法,因此可以使用与方法相同的修饰符声明属性。 如果在接口中定义该属性或使用修饰符定义 MustOverride ,则必须省略属性正文和 End 构造;否则会发生编译时错误。

索引参数列表构成属性的签名,因此可以在索引参数上重载属性,但不能重载属性的类型。 索引参数列表与常规方法相同。 但是,任何参数都不能与修饰符一起 ByRef 修改,也没有一个参数可以命名 Value (这是为访问器中的隐式值参数保留的 Set )。

属性可以声明如下:

  • 如果该属性未指定属性类型修饰符,则属性必须同时 Get 具有访问器和 Set 访问器。 该房产据说是读写属性。

  • 如果该属性指定 ReadOnly 修饰符,则属性必须具有 Get 访问器,并且可能没有 Set 访问器。 据说该房产是只读的。 将只读属性作为赋值目标会导致编译时错误。

  • 如果该属性指定 WriteOnly 修饰符,则属性必须具有 Set 访问器,并且可能没有 Get 访问器。 据说该房产是仅写属性。 在表达式中引用只写属性是编译时错误,只是作为赋值的目标或方法的参数。

属性 GetSet 访问器不是不同的成员,不能单独声明属性的访问器。 以下示例不声明单个读写属性。 相反,它声明两个具有相同名称的属性,一个只读属性和一个只写属性:

Class A
    Private nameValue As String

    ' Error, contains a duplicate member name.
    Public ReadOnly Property Name() As String 
        Get
            Return nameValue
        End Get
    End Property

    ' Error, contains a duplicate member name.
    Public WriteOnly Property Name() As String 
        Set (Value As String)
            nameValue = value
        End Set
    End Property
End Class

由于在同一类中声明的两个成员不能具有相同的名称,该示例会导致编译时错误。

默认情况下,属性 GetSet 访问器的可访问性与属性本身的可访问性相同。 但是, GetSet 访问器还可以单独指定辅助功能与属性。 在这种情况下,访问器的可访问性必须比属性的可访问性更严格,只有一个访问器可以具有与属性不同的辅助功能级别。 访问类型被视为或多或少限制,如下所示:

  • Private限制性大于PublicProtected FriendProtectedFriend

  • FriendProtected Friend 或更 Public严格。

  • ProtectedProtected Friend 或更 Public严格。

  • Protected Friend比限制性强。Public

当某个属性的访问器可访问但另一个访问器不是时,该属性将被视为只读或只写。 例如:

Class A
    Public Property P() As Integer
        Get
            ...
        End Get

        Private Set (Value As Integer)
            ...
        End Set
    End Property
End Class

Module Test
    Sub Main()
        Dim a As A = New A()

        ' Error: A.P is read-only in this context.
        a.P = 10
    End Sub
End Module

当派生类型对属性进行阴影时,派生属性将隐藏与读取和写入相关的阴影属性。 在以下示例中BP,在P读取和写入方面隐藏属性A

Class A
    Public WriteOnly Property P() As Integer
        Set (Value As Integer)
        End Set
    End Property
End Class

Class B
    Inherits A

    Public Shadows ReadOnly Property P() As Integer
       Get
       End Get
    End Property
End Class

Module Test
    Sub Main()
        Dim x As B = New B

        B.P = 10     ' Error, B.P is read-only.
    End Sub
End Module

返回类型或参数类型的辅助功能域必须与属性本身的辅助功能域或超集相同。 属性可能只有一个 Set 访问器和一个 Get 访问器。

除了声明和调用语法的差异、OverridableNotOverridableOverridesMustOverrideMustInherit属性的行为与方法MustInheritNotOverridableMustOverrideOverrides完全相同。Overridable 重写属性时,重写属性的类型必须相同(读写、只读、仅写)。 属性 Overridable 不能包含 Private 访问器。

在下面的示例 X 中,是 Overridable 只读属性, Y 是一个 Overridable 读写属性,也是 Z 一个 MustOverride 读写属性。

MustInherit Class A
    Private _y As Integer

    Public Overridable ReadOnly Property X() As Integer
        Get
            Return 0
        End Get
    End Property

    Public Overridable Property Y() As Integer
        Get
            Return _y
         End Get
        Set (Value As Integer)
            _y = value
        End Set
    End Property

    Public MustOverride Property Z() As Integer
End Class

因为ZMustOverride,必须声明MustInherit包含类A

相比之下,派生自类的类 A 如下所示:

Class B
    Inherits A

    Private _z As Integer

    Public Overrides ReadOnly Property X() As Integer
        Get
            Return MyBase.X + 1
        End Get
    End Property

    Public Overrides Property Y() As Integer
        Get
            Return MyBase.Y
        End Get
        Set (Value As Integer)
            If value < 0 Then
                MyBase.Y = 0
            Else
                MyBase.Y = Value
            End If
        End Set
    End Property

    Public Overrides Property Z() As Integer
        Get
            Return _z
        End Get
        Set (Value As Integer)
            _z = Value
        End Set
    End Property
End Class

此处,属性XY声明,并Z重写基属性。 每个属性声明都与相应继承属性的辅助功能修饰符、类型和名称完全匹配。 Get属性XSet的访问器和属性Y访问器使用MyBase关键字来访问继承的属性。 属性声明将重写属性 Z -- 因此,类B中没有未完成MustOverride的成员,并且B被允许为常规MustOverride类。

属性可用于延迟资源的初始化,直到首次引用资源的那一刻。 例如:

Imports System.IO

Public Class ConsoleStreams
    Private Shared reader As TextReader
    Private Shared writer As TextWriter
    Private Shared errors As TextWriter

    Public Shared ReadOnly Property [In]() As TextReader
        Get
            If reader Is Nothing Then
                reader = Console.In
            End If
            Return reader
        End Get
    End Property

    Public Shared ReadOnly Property Out() As TextWriter
        Get
            If writer Is Nothing Then
                writer = Console.Out
            End If
            Return writer
        End Get
    End Property

    Public Shared ReadOnly Property [Error]() As TextWriter
        Get
            If errors Is Nothing Then
                errors = Console.Error
            End If
            Return errors
        End Get
    End Property
End Class

ConsoleStreams 类包含三个属性,InOutError,分别表示标准输入、输出和错误设备。 通过将这些成员公开为属性, ConsoleStreams 类可以延迟其初始化,直到实际使用它们。 例如,首次引用 Out 属性时,如中所示 ConsoleStreams.Out.WriteLine("hello, world"),初始化输出设备的基础 TextWriter 。 但是,如果应用程序没有引用 InError 属性,则不会为这些设备创建任何对象。

获取访问器声明

Get使用属性Get声明声明访问器(getter)。 属性 Get 声明由后跟语句块的关键字 Get 组成。 给定名为 P的属性时, Get 访问器声明隐式声明具有与属性相同的修饰符、类型和参数列表的方法 get_P 。 如果该类型包含具有该名称的声明,则会导致编译时错误,但出于名称绑定的目的,将忽略隐式声明。

在访问器正文声明空间中 Get 隐式声明与属性同名的特殊局部变量表示属性的返回值。 局部变量在表达式中使用时具有特殊的名称解析语义。 如果在需要分类为方法组的表达式(如调用表达式)的上下文中使用局部变量,则名称将解析为函数而不是局部变量。 例如:

ReadOnly Property F(i As Integer) As Integer
    Get
        If i = 0 Then
            F = 1    ' Sets the return value.
        Else
            F = F(i - 1) ' Recursive call.
        End If
    End Get
End Property

使用括号可能会导致不明确的情况(例如 F(1) ,其 F 类型为一维数组的属性的位置)。 在所有不明确的情况下,名称解析为属性而不是局部变量。 例如:

ReadOnly Property F(i As Integer) As Integer()
    Get
        If i = 0 Then
            F = new Integer(2) { 1, 2, 3 }
        Else
            F = F(i - 1) ' Recursive call, not index.
        End If
    End Get
End Property

当控制流离开 Get 访问器正文时,局部变量的值将传回调用表达式。 由于调用 Get 访问器在概念上等效于读取变量的值,因此将访问器的编程样式视为具有可观察副作用的不良编程样式 Get ,如以下示例所示:

Class Counter
    Private Value As Integer

    Public ReadOnly Property NextValue() As Integer
        Get
            Value += 1
            Return Value
        End Get
    End Property
End Class

属性的值 NextValue 取决于以前访问该属性的次数。 因此,访问该属性会产生可观察的副作用,而应以方法的形式实现该属性。

访问器的“无副作用”约定 Get 并不意味着 Get 应始终写入访问器以仅返回存储在变量中的值。 事实上, Get 访问器通常通过访问多个变量或调用方法来计算属性的值。 但是,设计正确的 Get 访问器不会执行任何导致对象状态可观察更改的作。

注意。 Get 访问器对子例程具有的行放置具有相同的限制。 开头语句、end 语句和块必须全部显示在逻辑行的开头。

PropertyGetDeclaration
    : Attributes? AccessModifier? 'Get' LineTerminator
      Block?
      'End' 'Get' StatementTerminator
    ;

设置访问器声明

Set使用属性集声明声明访问器(setter)。 属性集声明由关键字 Set、可选参数列表和语句块组成。 给定名为 P的属性时,setter 声明隐式声明具有与属性相同的修饰符和参数列表的名称 set_P 的方法。 如果该类型包含具有该名称的声明,则会导致编译时错误,但出于名称绑定的目的,将忽略隐式声明。

如果指定了参数列表,则它必须具有一个成员,该成员必须没有修饰符, ByVal并且其类型必须与属性的类型相同。 该参数表示要设置的属性值。 如果省略该参数,则会隐式声明一个名为 Value 的参数。

注意。 Set 访问器对子例程具有的行放置具有相同的限制。 开头语句、end 语句和块必须全部显示在逻辑行的开头。

PropertySetDeclaration
    : Attributes? AccessModifier? 'Set'
      ( OpenParenthesis ParameterList? CloseParenthesis )? LineTerminator
      Block?
      'End' 'Set' StatementTerminator
    ;

默认属性

指定修饰符 Default 的属性称为 默认属性。 允许属性的任何类型都有默认属性,包括接口。 可以引用默认属性,而无需使用属性的名称限定实例。 因此,给定类

Class Test
    Public Default ReadOnly Property Item(i As Integer) As Integer
        Get
            Return i
        End Get
    End Property
End Class

代码

Module TestModule
    Sub Main()
        Dim x As Test = New Test()
        Dim y As Integer

        y = x(10)
    End Sub
End Module

等效于

Module TestModule
    Sub Main()
        Dim x As Test = New Test()
        Dim y As Integer

        y = x.Item(10)
    End Sub
End Module

声明 Default属性后,继承层次结构中该名称上重载的所有属性都将成为默认属性,无论它们是否已声明 Default 。 当基类以其他名称声明默认属性时,在派生类中声明属性 Default 不需要任何其他修饰符,例如 ShadowsOverrides。 这是因为默认属性没有标识或签名,因此无法隐藏或重载。 例如:

Class Base
    Public ReadOnly Default Property Item(i As Integer) As Integer
        Get
            Console.WriteLine("Base = " & i)
        End Get
    End Property
End Class

Class Derived
    Inherits Base

    ' This hides Item, but does not change the default property.
    Public Shadows ReadOnly Property Item(i As Integer) As Integer
        Get
            Console.WriteLine("Derived = " & i)
        End Get
    End Property
End Class

Class MoreDerived
    Inherits Derived

    ' This declares a new default property, but not Item.
    ' This does not need to be declared Shadows
    Public ReadOnly Default Property Value(i As Integer) As Integer
        Get
            Console.WriteLine("MoreDerived = " & i)
        End Get
    End Property
End Class

Module Test
    Sub Main()
        Dim x As MoreDerived = New MoreDerived()
        Dim y As Integer
        Dim z As Derived = x

        y = x(10)        ' Calls MoreDerived.Value.
        y = x.Item(10)   ' Calls Derived.Item
        y = z(10)        ' Calls Base.Item
    End Sub
End Module

此程序将生成输出:

MoreDerived = 10
Derived = 10
Base = 10

在类型中声明的所有默认属性都必须具有相同的名称,为清楚起见,必须指定 Default 修饰符。 由于没有索引参数的默认属性在分配包含类的实例时会导致不明确的情况,因此默认属性必须具有索引参数。 此外,如果特定名称上重载的一个属性包括 Default 修饰符,则该名称上重载的所有属性都必须指定它。 默认属性可能Shared不是,并且属性的至少一个访问器不得。Private

自动实现的属性

如果属性省略任何访问器的声明,则除非在接口中声明该属性或声明该属性,否则将自动提供该属性的 MustOverride实现。 只能自动实现不带参数的读/写属性;否则,将发生编译时错误。

自动实现的属性 x(甚至重写另一个属性)引入了一个与属性具有相同类型的私有局部变量 _x 。 如果局部变量的名称与另一个声明之间存在冲突,则会报告编译时错误。 自动实现的属性 Get 访问器返回本地值和设置本地值的属性 Set 访问器的值。 例如,声明:

Public Property x() As Integer

大致等效于:

Private _x As Integer
Public Property x() As Integer
    Get
        Return _x
    End Get
    Set (value As Integer)
        _x = value
    End Set
End Property

与变量声明一样,自动实现的属性可以包含初始值设定项。 例如:

Public Property x() As Integer = 10
Public Shared Property y() As New Customer() With { .Name = "Bob" }

注意。 初始化自动实现的属性时,将通过属性而不是基础字段初始化该属性。 这样,重写属性可以截获初始化(如果需要)。

自动实现的属性允许使用数组初始值设定项,只是无法显式指定数组边界。 例如:

' Valid
Property x As Integer() = {1, 2, 3}
Property y As Integer(,) = {{1, 2, 3}, {12, 13, 14}, {11, 10, 9}}

' Invalid
Property x4(5) As Short

迭代器属性

迭代器属性是具有Iterator修饰符的属性。 它用于迭代器方法(Section Iterator Methods)的相同原因 -- 作为生成序列的便捷方法,该序列可由语句使用 For EachGet迭代器属性的访问器与迭代器方法的解释方式相同。

迭代器属性必须具有显式Get访问器,并且其类型必须为IEnumeratorIEnumerableIEnumerator(Of T)IEnumerable(Of T)某些T对象。

下面是迭代器属性的示例:

Class Family
    Property Daughters As New List(Of String) From {"Beth", "Diane"}
    Property Sons As New List(Of String) From {"Abe", "Carl"}

    ReadOnly Iterator Property Children As IEnumerable(Of String)
        Get
            For Each name In Daughters : Yield name : Next
            For Each name In Sons : Yield name : Next
        End Get
    End Property
End Class

Module Module1
    Sub Main()
        Dim x As New Family
        For Each c In x.Children
            Console.WriteLine(c) ' prints Beth, Diane, Abe, Carl
        Next
    End Sub
End Module

运营商

运算符 是定义包含类的现有 Visual Basic 运算符的含义的方法。 当运算符应用于表达式中的类时,运算符将编译为对类中定义的运算符方法的调用。 为类定义运算符也称为 重载 运算符。

OperatorDeclaration
    : Attributes? OperatorModifier* 'Operator' OverloadableOperator
      OpenParenthesis ParameterList CloseParenthesis
      ( 'As' Attributes? TypeName )? LineTerminator
      Block?
      'End' 'Operator' StatementTerminator
    ;

OperatorModifier
    : 'Public' | 'Shared' | 'Overloads' | 'Shadows' | 'Widening' | 'Narrowing'
    ;

OverloadableOperator
    : '+' | '-' | '*' | '/' | '\\' | '&' | 'Like' | 'Mod' | 'And' | 'Or' | 'Xor'
    | '^' | '<' '<' | '>' '>' | '=' | '<' '>' | '>' | '<' | '>' '=' | '<' '='
    | 'Not' | 'IsTrue' | 'IsFalse' | 'CType'
    ;

无法重载已存在的运算符;实际上,这主要适用于转换运算符。 例如,无法重载从派生类到基类的转换:

Class Base
End Class

Class Derived
    ' Cannot redefine conversion from Derived to Base,
    ' conversion will be ignored.
    Public Shared Widening Operator CType(s As Derived) As Base
        ...
    End Operator
End Class

运算符也可以在单词的常识中重载:

Class Base
    Public Shared Widening Operator CType(b As Base) As Integer
        ...
    End Operator

    Public Shared Narrowing Operator CType(i As Integer) As Base
        ...
    End Operator
End Class

运算符声明不会向包含类型的声明空间显式添加名称;但是,它们确实以字符“op_”开头隐式声明相应的方法。 以下各节列出了每个运算符的相应方法名称。

可以定义三类运算符:一元运算符、二元运算符和转换运算符。 所有运算符声明共享某些限制:

  • 运算符声明必须始终为 PublicSharedPublic可以在假定修饰符的上下文中省略修饰符。

  • 无法声明 ByRef运算符的参数, Optional 或者 ParamArray

  • 至少一个作数或返回值的类型必须是包含运算符的类型。

  • 没有为运算符定义的函数返回变量。 因此, Return 语句必须用于从运算符正文返回值。

这些限制的唯一例外适用于可为 null 的值类型。 由于可以为 null 的值类型没有实际类型定义,因此值类型可以为该类型的可为 null 版本声明用户定义的运算符。 确定类型是否可以声明特定用户定义运算符时, ? 将首先从声明中涉及的所有类型中删除修饰符,以便进行有效性检查。 这种放松不适用于返回类型和IsTrueIsFalse运算符;它们仍必须返回Boolean,而不是Boolean?

运算符的优先级和关联性不能由运算符声明修改。

注意。 运算符对子例程具有的行放置具有相同的限制。 开头语句、end 语句和块必须全部显示在逻辑行的开头。

一元运算符

可以重载以下一元运算符:

  • 一元加运算符 + (相应的方法: op_UnaryPlus

  • 一元减号运算符 - (相应的方法: op_UnaryNegation

  • 逻辑 Not 运算符(相应的方法: op_OnesComplement

  • IsTrueIsFalse运算符 (相应的方法: op_Trueop_False

所有重载的一元运算符都必须采用包含类型的单个参数,并且可以返回任何类型,但必须IsTrueIsFalse返回Boolean。 如果包含类型是泛型类型,则类型参数必须与包含类型的类型参数匹配。 例如,

Structure Complex
    ...

    Public Shared Operator +(v As Complex) As Complex
        Return v
    End Operator
End Structure

如果类型重载其中一种或IsFalse重载,则还必须重载另一种IsTrue类型。 如果仅重载一个,则编译时错误结果。

注意。 IsTrue 不是 IsFalse 保留字。

二进制运算符

可以重载以下二进制运算符:

  • +法、减法-、乘*法、除/法、整数除\法、模Mod数和指数^运算符(对应方法:op_Addition、、、op_Subtractionop_Multiplyop_IntegerDivisionop_Division、) op_Exponentop_Modulus

  • 关系运算符=、、、<><=>= (相应的方法:op_Equality、、、op_Inequalityop_LessThanop_GreaterThanop_LessThanOrEqualop_GreaterThanOrEqual)。 <> 注意。 虽然可重载相等运算符,但赋值运算符(仅在赋值语句中使用)不能重载。

  • 运算符 Like (相应的方法: op_Like

  • 串联运算符 & (相应的方法: op_Concatenate

  • 逻辑AndOr运算符和Xor运算符(相应的方法:op_BitwiseAnd、、 op_BitwiseOrop_ExclusiveOr

  • Shift 运算符 <<>> (相应的方法: op_LeftShiftop_RightShift

所有重载的二进制运算符都必须将包含类型作为参数之一。 如果包含类型是泛型类型,则类型参数必须与包含类型的类型参数匹配。 移位运算符进一步限制此规则,要求第一个参数为包含类型;第二个参数必须始终为类型 Integer

必须以对形式声明以下二进制运算符:

  • 运算符 = 和运算符 <>

  • 运算符 > 和运算符 <

  • 运算符 >= 和运算符 <=

如果声明了其中一个对,则另一对还必须使用匹配的参数和返回类型进行声明,否则将生成编译时错误。 (注意。 要求关系运算符的配对声明的目的是尝试并确保在重载运算符中至少达到最小逻辑一致性级别。

与关系运算符相反,强烈建议不要重载除法和整型除法运算符,尽管不是错误。 (注意。 一般来说,这两种类型的除法应该完全不同:支持除法的类型要么是整型(在这种情况下它应该支持 \),要么不是(在这种情况下,它应该支持 /)。 我们考虑使这两个运算符定义错误,但由于其语言通常不区分两种类型的划分方式 Visual Basic,我们认为允许这种做法是安全的,但强烈劝阻它。

复合赋值运算符不能直接重载。 相反,当相应的二进制运算符重载时,复合赋值运算符将使用重载运算符。 例如:

Structure Complex
    ...

    Public Shared Operator +(x As Complex, y As Complex) _
        As Complex
        ...
    End Operator
End Structure

Module Test
    Sub Main()
        Dim c1, c2 As Complex
        ' Calls the overloaded + operator
        c1 += c2
    End Sub
End Module

转换运算符

转换运算符定义类型之间的新转换。 这些新转换称为 用户定义的转换。 转换运算符从源类型(由转换运算符的参数类型指示)转换为目标类型,由转换运算符的返回类型指示。 转换必须归类为扩大或缩小。 包含关键字的 Widening 转换运算符声明引入了用户定义的扩大转换(相应的方法: op_Implicit) 。 包含关键字的 Narrowing 转换运算符声明引入了用户定义的缩小转换(相应的方法: op_Explicit) 。

一般情况下,用户定义扩大转换的设计应永远不会引发异常,也不会丢失信息。 如果用户定义的转换可能导致异常(例如源参数范围不足)或信息丢失(如放弃高阶位),则应将转换定义为缩小转换。 在示例中:

Structure Digit
    Dim value As Byte

    Public Sub New(value As Byte)
        if value < 0 OrElse value > 9 Then Throw New ArgumentException()
        Me.value = value
    End Sub

    Public Shared Widening Operator CType(d As Digit) As Byte
        Return d.value
    End Operator

    Public Shared Narrowing Operator CType(b As Byte) As Digit
        Return New Digit(b)
    End Operator
End Structure

DigitByte 的转换是一个扩大的转换,因为它永远不会引发异常或丢失信息,但从 ByteDigit 的转换是一个缩小的转换,因为 Digit 只能表示一个可能的值的 Byte子集。

与可以重载的所有其他类型成员不同,转换运算符的签名包括转换的目标类型。 这是返回类型参与签名的唯一类型成员。 但是,转换运算符的扩大或缩小分类不是运算符签名的一部分。 因此,类或结构不能同时声明扩大转换运算符和具有相同源和目标类型的缩小转换运算符。

用户定义的转换运算符必须转换到包含类型或从包含类型转换,例如,类C可以定义从CInteger和从IntegerC的转换,但不能从Integer中转换。Boolean 如果包含类型是泛型类型,则类型参数必须与包含类型的类型参数匹配。 此外,无法重新定义内部函数(即非用户定义的)转换。 因此,类型无法声明转换,其中:

  • 源类型和目标类型相同。

  • 源类型和目标类型不是定义转换运算符的类型。

  • 源类型或目标类型是接口类型。

  • 源类型和目标类型由继承(包括 Object) 相关。

这些规则的唯一例外适用于可为 null 的值类型。 由于可以为 null 的值类型没有实际类型定义,因此值类型可以为该类型的可为 null 版本声明用户定义的转换。 确定类型是否可以声明特定的用户定义的转换时, ? 将首先从声明中涉及的所有类型中删除修饰符,以便进行有效性检查。 因此,以下声明有效,因为 S 可以定义从以下转换 ST

Structure T
    ...
End Structure

Structure S
    Public Shared Widening Operator CType(ByVal v As S?) As T
    ...
    End Operator
End Structure

但是,以下声明无效,因为结构 S 无法定义从以下转换 SS

Structure S
    Public Shared Widening Operator CType(ByVal v As S) As S?
        ...
    End Operator
End Structure

运算符映射

由于 Visual Basic 支持的运算符集可能与 .NET Framework 上其他语言的运算符集完全匹配,因此在定义或使用时,某些运算符会专门映射到其他运算符。 具体说来:

  • 定义整型除法运算符将自动定义常规除法运算符(仅适用于其他语言),该运算符将调用整除运算符。

  • Not载和AndOr运算符仅从区分逻辑运算符和按位运算符的其他语言的角度重载按位运算符。

  • 仅重载逻辑运算符的语言中的逻辑运算符(即使用op_LogicalNotop_LogicalAndop_LogicalOr、使用、用于和用于NotAndOr的语言)的类,其逻辑运算符将映射到 Visual Basic 逻辑运算符。 如果逻辑运算符和按位运算符都重载,则仅使用按位运算符。

  • << 载和 >> 运算符将仅从区分已签名移位运算符和无符号移位运算符的其他语言的角度重载。

  • 仅重载无符号班次运算符的类将无符号班次运算符映射到相应的 Visual Basic 班次运算符。 如果重载无符号和已签名的 Shift 运算符,则只会使用已签名的 Shift 运算符。