通过


语句 - Visual Basic

语句表示可执行代码。

Statement
    : LabelDeclarationStatement
    | LocalDeclarationStatement
    | WithStatement
    | SyncLockStatement
    | EventStatement
    | AssignmentStatement
    | InvocationStatement
    | ConditionalStatement
    | LoopStatement
    | ErrorHandlingStatement
    | BranchStatement
    | ArrayHandlingStatement
    | UsingStatement
	| AwaitStatement
	| YieldStatement
    ;

注意。 Microsoft Visual Basic 编译器仅允许以关键字或标识符开头的语句。 因此,例如,允许调用语句“”Call (Console).WriteLine“,但调用语句”(Console).WriteLine“不是。

控制流

控制流 是执行语句和表达式的顺序。 执行顺序取决于特定语句或表达式。

例如,在计算加法运算符(Section 加法运算符)时,首先计算左作数,然后计算右作数,然后计算运算符本身。 块通过首先执行其第一个子语句来执行(分区 块和标签),然后逐个执行块的语句。

此顺序中的隐式是 控制点的概念,这是要执行的下一个作。 调用方法(或“调用”)时,我们说该方法会创建方法 的实例 。 方法实例包含该方法的参数和局部变量及其自己的控制点的副本。

常规方法

下面是常规方法的示例

Function Test() As Integer
    Console.WriteLine("hello")
    Return 1
End Function

Dim x = Test()    ' invokes the function, prints "hello", assigns 1 to x

调用常规方法时,

  1. 首先创建特定于该调用的方法的实例。 此实例包含方法的所有参数和局部变量的副本。
  2. 然后,其所有参数都会初始化为提供的值,并将其所有局部变量初始化为其类型的默认值。
  3. 在这种情况下 Function,还初始化了一个隐式局部 变量 ,该变量的名称是函数的名称,其类型为函数的返回类型,其初始值为其类型的默认值。
  4. 然后,方法实例的控制点在方法正文的第一个语句处设置,方法正文立即从该处开始执行(节 块和标签)。

当控制流正常退出方法主体时(通过到达 End FunctionEnd Sub 标记其末尾或通过显式 ReturnExit 语句)控制流将返回到方法实例的调用方。 如果有函数返回变量,则调用的结果是此变量的最终值。

当控制流通过未经处理的异常退出方法正文时,该异常将传播到调用方。

控制流退出后,不再有任何对方法实例的实时引用。 如果方法实例只包含对其本地变量或参数副本的引用,则可能会进行垃圾回收。

迭代器方法

迭代器方法用作生成序列的便捷方法,该序列可由语句使用 For Each 。 迭代器方法使用 Yield 语句(Section Yield 语句)提供序列的元素。 (不 Yield 带语句的迭代器方法将生成空序列)。 下面是迭代器方法的示例:

Iterator Function Test() As IEnumerable(Of Integer)
    Console.WriteLine("hello")
    Yield 1
    Yield 2
End Function

Dim en = Test()
For Each x In en          ' prints "hello" before the first x
    Console.WriteLine(x)  ' prints "1" and then "2"
Next

调用其返回类型为 IEnumerator(Of T) 的迭代器方法时,

  1. 第一个迭代器方法的实例是特定于该调用创建的。 此实例包含方法的所有参数和局部变量的副本。
  2. 然后,其所有参数都会初始化为提供的值,并将其所有局部变量初始化为其类型的默认值。
  3. 隐式局部变量也初始化为 迭代器当前变量,其类型为 T 初始值为其类型默认值。
  4. 然后,方法实例的控制点在方法正文的第一个语句处设置。
  5. 然后创建与此方法实例关联的 迭代器对象 。 迭代器对象实现声明的返回类型,并具有以下行为。
  6. 然后,在调用方中 立即 恢复控制流,调用的结果是迭代器对象。 请注意,此传输是在不退出迭代器方法实例的情况下完成的,并且不会导致最终处理程序执行。 方法实例仍由迭代器对象引用,并且只要存在对迭代器对象的实时引用,就不会进行垃圾回收。

访问迭代器对象的 Current 属性时,将返回调用的 当前变量

调用迭代器对象 MoveNext 的方法时,调用不会创建新的方法实例。 而是使用现有方法实例(及其控制点和局部变量和参数)-首次调用迭代器方法时创建的实例。 控制流在该方法实例的控制点恢复执行,并按正常方式继续执行迭代器方法的主体。

调用迭代器对象 Dispose 的方法时,再次使用现有方法实例。 控制流在该方法实例的控制点恢复,但随后会立即像语句是下一个 Exit Function 作一样。

上述迭代器对象调用 MoveNextDispose 迭代器对象的行为说明仅适用于该迭代器对象的所有先前调用 MoveNextDispose 已返回到其调用方的行为。 如果它们尚未定义,则行为是未定义的。

当控制流正常退出迭代器方法主体时(通过到达End Function标记其末尾或通过显式ReturnExit语句)时,它必须在迭代器对象调用或Dispose函数的上下文MoveNext中执行此作,以恢复迭代器方法实例,并且它已使用首次调用迭代器方法时创建的方法实例。 该实例的控制点保留在语句中 End Function ,并且控制流在调用方中恢复;如果调用已恢复 MoveNext 该实例,则该值 False 将返回到调用方。

当控制流通过未经处理的异常退出迭代器方法正文时,异常将传播到调用方,后者将再次调用或Dispose调用MoveNext

至于迭代器函数的其他可能返回类型,

  • 调用其返回类型为 IEnumerable(Of T) 某些 T类型的迭代器方法时,首先会创建一个实例(特定于该方法中所有参数的迭代器方法调用),并使用提供的值初始化这些参数。 调用的结果是实现返回类型的对象。 如果调用此对象的 GetEnumerator 方法,它将创建一个实例,该实例特定于该方法中所有参数和局部变量的 GetEnumerator 调用。 它将参数初始化已保存的值,并继续执行上述迭代器方法。
  • 当调用其返回类型为非泛型接口 IEnumerator的迭代器方法时,行为完全与原 IEnumerator(Of Object)样。
  • 当调用其返回类型为非泛型接口 IEnumerable的迭代器方法时,行为完全与原 IEnumerable(Of Object)样。

异步方法

异步方法是执行长时间运行的工作的便捷方法,例如阻止应用程序的 UI。 异步的 异步 短 - 这意味着异步方法的调用方将立即恢复其执行,但异步方法实例的最终完成可能在以后的某个时间发生。 按照约定,异步方法以后缀“Async”命名。

Async Function TestAsync() As Task(Of String)
    Console.WriteLine("hello")
    Await Task.Delay(100)
    Return "world"
End Function

Dim t = TestAsync()         ' prints "hello"
Console.WriteLine(Await t)  ' prints "world"

注意。 异步方法 不在 后台线程上运行。 相反,它们允许方法通过 Await 运算符暂停自身,并计划自行恢复以响应某些事件。

调用异步方法时

  1. 首先,创建特定于该调用的异步方法的实例。 此实例包含方法的所有参数和局部变量的副本。
  2. 然后,其所有参数都会初始化为提供的值,并将其所有局部变量初始化为其类型的默认值。
  3. 对于具有某些T返回类型的Task(Of T)异步方法,还初始化了一个隐式局部变量,称为任务返回变量,其类型为T,其初始值为默认值T
  4. 如果异步方法是Function具有返回类型或Task(Of T)某些T方法Task,则隐式创建该类型的对象(与当前调用相关联)。 这称为 异步对象 ,表示将通过执行异步方法实例完成的未来工作。 当控件在此异步方法实例的调用方中恢复时,调用方将接收此异步对象作为调用的结果。
  5. 然后,在异步方法正文的第一个语句中设置实例的控制点,然后立即从该处开始执行方法正文(节 块和标签)。

恢复委托和当前调用方

如 Section Await 运算符中所述,表达式的执行 Await 能够暂停方法实例的控制点,使控制流离开其他位置。 控制流稍后可以通过调用 恢复委托在同一实例的控制点恢复。 请注意,此挂起是在不退出异步方法的情况下完成的,并且不会导致最终处理程序执行。 方法实例仍由恢复委托和 Task 结果 Task(Of T) (如果有)引用,并且不会进行垃圾回收,只要存在对委托或结果的实时引用。

假设语句 Dim x = Await WorkAsync() 大致为语法简写,这很有帮助:

Dim temp = WorkAsync().GetAwaiter()
If Not temp.IsCompleted Then
       temp.OnCompleted(resumptionDelegate)
       Return [task]
       CONT:   ' invocation of 'resumptionDelegate' will resume here
End If
Dim x = temp.GetResult()

在以下情况下,方法实例的 当前调用方 定义为原始调用方或恢复委托的调用方,以较最近为准。

当控制流退出异步方法正文(通过到达 End SubEnd Function 标记其末尾或通过显式 ReturnExit 语句,或通过未经处理的异常)时,实例的控制点将设置为方法的末尾。 然后,行为取决于异步方法的返回类型。

  • 对于 Async Function 返回类型 Task为 :

    1. 如果控制流通过未经处理的异常退出,则异步对象的状态设置为 TaskStatus.Faulted 该异常,其 Exception.InnerException 属性设置为异常(但:某些实现定义的异常,如 OperationCanceledException 将其 TaskStatus.Canceled更改为)。 控制流在当前调用方中恢复。

    2. 否则,异步对象的状态设置为 TaskStatus.Completed。 控制流在当前调用方中恢复。

      注意。 任务的全部要点,以及使异步方法变得有趣的是,当任务变为“已完成”时,等待它的任何方法现在都将执行其恢复委托,即它们将被取消阻止。

  • 对于Async Function某些T类型的返回类型Task(Of T):行为如上所示,不同之处在于,在非异常情况下,异步对象的Result属性也设置为任务返回变量的最终值。

  • 在以下情况下 Async Sub

    1. 如果控制流通过未经处理的异常退出,则该异常以某种特定于实现的方式传播到环境。 控制流在当前调用方中恢复。
    2. 否则,控制流只会在当前调用方中恢复。

异步子

存在一些特定于Microsoft的行为 Async Sub

如果 SynchronizationContext.Current 正在 Nothing 调用开始时,则异步子中的任何未经处理的异常都将发布到 Threadpool。

如果 SynchronizationContext.Current 不是 Nothing 在调用开始时, OperationStarted() 则在方法的开头和 OperationCompleted() 末尾之前在该上下文上调用。 此外,任何未经处理的异常都将发布到同步上下文中重新引发。

这意味着,在 UI 应用程序中,对于 Async Sub 在 UI 线程上调用的异常,它无法处理的任何异常都将重新发布 UI 线程。

异步和迭代器方法中的可变结构

一般情况下,可变结构被视为不良做法,异步或迭代器方法不支持这些结构。 具体而言,结构中异步或迭代器方法的每个调用都将隐式作该结构 的副本 ,该结构在调用时复制。 因此,例如

Structure S
       Dim x As Integer
       Async Sub Mutate()
           x = 2
       End Sub
End Structure

Dim s As New S With {.x = 1}
s.Mutate()
Console.WriteLine(s.x)   ' prints "1"

块和标签

一组可执行语句称为语句块。 语句块的执行以块中的第一个语句开头。 执行语句后,将按词法顺序执行下一个语句,除非语句将执行到其他位置或发生异常。

在语句块中,逻辑行上的语句除法并不重要,但标签声明语句除外。 标签是标识语句块中特定位置的标识符,可用作分支语句的目标,例如 GoTo

Block
    : Statements*
    ;

LabelDeclarationStatement
    : LabelName ':'
    ;

LabelName
    : Identifier
    | IntLiteral
    ;

Statements
    : Statement? ( ':' Statement? )*
    ;

标签声明语句必须出现在逻辑行的开头,标签可以是标识符或整数文本。 由于标签声明语句和调用语句都可以包含单个标识符,因此本地行开头的单个标识符始终被视为标签声明语句。 标签声明语句必须始终后跟冒号,即使没有语句遵循同一逻辑行。

标签具有自己的声明空间,不会干扰其他标识符。 以下示例有效,并将名称变量 x 既用作参数,又用作标签。

Function F(x As Integer) As Integer
    If x >= 0 Then
        GoTo x
    End If
    x = -x
x: 
    Return x
End Function

标签的范围是包含标签的方法的正文。

为了提高可读性,涉及多个子语句的语句生产被视为此规范中的单个生产,尽管子语句可能各自位于带标签的行上。

局部变量和参数

前面的部分详细介绍了创建方法实例的方式和时间,以及方法的局部变量和参数的副本。 此外,每次输入循环正文时,都会根据 Section Loop 语句中所述,在该循环中声明的每个局部变量生成一个新副本,并且方法实例现在包含其局部变量的此副本,而不是上一个副本。

所有局部变量都初始化为其类型的默认值。 局部变量和参数始终可公开访问。 在声明前的文本位置引用局部变量是错误的,如以下示例所示:

Class A
    Private i As Integer = 0

    Sub F()
        i = 1
        Dim i As Integer       ' Error, use precedes declaration.
        i = 2
    End Sub

    Sub G()
        Dim a As Integer = 1
        Dim b As Integer = a   ' This is valid.
    End Sub
End Class

在上述方法中 F ,要专门分配的第一个赋值 i 不引用在外部作用域中声明的字段。 相反,它引用局部变量,并且它出错,因为它在变量声明之前进行文本表示。 在该方法中 G ,后续变量声明是指在同一局部变量声明的早期变量声明中声明的局部变量。

方法中的每个块都会为局部变量创建声明空间。 名称通过方法正文中的局部变量声明和方法的参数列表将此声明空间引入到此声明空间中,该方法将名称引入到最外层块的声明空间中。 块不允许通过嵌套隐藏名称:在块中声明名称后,任何嵌套块中都可能不会重新声明该名称。

因此,在下面的示例中,F由于名称在外部块中声明,并且无法在内部块中重新声明,i因此该方法出错G。 但是,由于H这两i个函数在单独的非嵌套块中声明,因此该方法有效I

Class A
    Sub F()
        Dim i As Integer = 0
        If True Then
               Dim i As Integer = 1
        End If
    End Sub

    Sub G()
        If True Then
            Dim i As Integer = 0
        End If
        Dim i As Integer = 1
    End Sub 

    Sub H()
        If True Then
            Dim i As Integer = 0
        End If
        If True Then
            Dim i As Integer = 1
        End If
    End Sub

    Sub I() 
        For i As Integer = 0 To 9
            H()
        Next i

        For i As Integer = 0 To 9
            H()
        Next i
    End Sub 
End Class

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

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

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

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

当控制流离开方法正文时,局部变量的值将传回调用表达式。 如果该方法是子例程,则不存在此类隐式局部变量,并且控件仅返回调用表达式。

本地声明语句

局部声明语句声明新的局部变量、局部常量或静态变量。 局部变量局部常量 等效于限定为方法的实例变量和常量,以相同的方式声明。 静态变量 类似于 Shared 变量,并使用 Static 修饰符声明。

LocalDeclarationStatement
    : LocalModifier VariableDeclarators StatementTerminator
    ;

LocalModifier
    : 'Static' | 'Dim' | 'Const'
    ;

静态变量是局部变量,用于在调用方法时保留其值。 在非共享方法中声明的静态变量是每个实例:包含该方法的类型的每个实例都有自己的静态变量副本。 在方法中 Shared 声明的静态变量是每种类型的;对于所有实例,静态变量只有一个副本。 虽然局部变量在方法中的每个条目时都初始化为其类型的默认值,但静态变量仅在初始化类型或类型实例时将其默认值初始化为其类型的默认值。 静态变量不能在结构或泛型方法中声明。

局部变量、局部常量和静态变量始终具有公共辅助功能,不能指定辅助功能修饰符。 如果未在本地声明语句上指定类型,则以下步骤确定本地声明的类型:

  1. 如果声明具有类型字符,则类型字符的类型是本地声明的类型。

  2. 如果本地声明是局部常量,或者本地声明是具有初始值设定项的局部变量,并且正在使用局部变量类型推理,则从初始值设定项的类型推断本地声明的类型。 如果初始值设定项引用本地声明,则会发生编译时错误。 (需要本地常量才能有初始值设定项。

  3. 如果未使用严格的语义,则本地声明语句的类型是 Object隐式的。

  4. 否则,会发生编译时错误。

如果在具有数组大小或数组类型修饰符的本地声明语句上未指定任何类型,则本地声明的类型是具有指定排名的数组,并且前面的步骤用于确定数组的元素类型。 如果使用局部变量类型推理,初始值设定项的类型必须是具有相同数组形状(即数组类型修饰符)作为本地声明语句的数组类型。 请注意,推断的元素类型可能仍然是数组类型。 例如:

Option Infer On

Module Test
    Sub Main()
        ' Error: initializer is not an array type
        Dim x() = 1

        ' Type is Integer()
        Dim y() = New Integer() {}

        ' Type is Integer()()
        Dim z() = New Integer()() {}

        ' Type is Integer()()()

        Dim a()() = New Integer()()() {}

        ' Error: initializer does not have same array shape
        Dim b()() = New Integer(,)() {}
    End Sub
End Module

如果没有对具有可为 null 类型修饰符的本地声明语句指定任何类型,则本地声明的类型为推断类型的可为 null 版本;如果已为 null 值类型,则为推断类型本身。 如果推断的类型不是可以为 null 的值类型,则会发生编译时错误。 如果可以为 null 的类型修饰符和数组大小或数组类型修饰符都放置在没有类型的本地声明语句上,则将可为 null 的类型修饰符视为应用于数组的元素类型,而前面的步骤用于确定元素类型。

本地声明语句上的变量初始值设定项等效于放置在声明的文本位置的赋值语句。 因此,如果对本地声明语句执行分支,则不会执行变量初始值设定项。 如果多次执行本地声明语句,则变量初始值设定项的执行次数相等。 静态变量仅在第一次执行其初始值设定项。 如果在初始化静态变量时发生异常,则静态变量将被视为使用静态变量类型的默认值进行初始化。

以下示例演示初始值设定项的使用:

Module Test
    Sub F()
        Static x As Integer = 5

        Console.WriteLine("Static variable x = " & x)
        x += 1
    End Sub

    Sub Main()
        Dim i As Integer

        For i = 1 to 3
            F()
        Next i

        i = 3
label:
        Dim y As Integer = 8

        If i > 0 Then
            Console.WriteLine("Local variable y = " & y)
            y -= 1
            i -= 1
            GoTo label
        End If
    End Sub
End Module

此程序打印:

Static variable x = 5
Static variable x = 6
Static variable x = 7
Local variable y = 8
Local variable y = 8
Local variable y = 8

静态局部变量上的初始值设定项是线程安全的,在初始化期间不受异常的保护。 如果在静态本地初始值设定项期间发生异常,则静态本地将具有其默认值,并且不会初始化。 静态本地初始值设定项

Module Test
    Sub F()
        Static x As Integer = 5
    End Sub
End Module

等效于

Imports System.Threading
Imports Microsoft.VisualBasic.CompilerServices

Module Test
    Class InitFlag
        Public State As Short
    End Class

    Private xInitFlag As InitFlag = New InitFlag()

    Sub F()
        Dim x As Integer

        If xInitFlag.State <> 1 Then
            Monitor.Enter(xInitFlag)
            Try
                If xInitFlag.State = 0 Then
                    xInitFlag.State = 2
                    x = 5
                Else If xInitFlag.State = 2 Then
                    Throw New IncompleteInitialization()
                End If
            Finally
                xInitFlag.State = 1
                Monitor.Exit(xInitFlag)
            End Try
        End If
    End Sub
End Module

局部变量、局部常量和静态变量的范围限定为声明它们的语句块。 静态变量特别,其名称只能在整个方法中使用一次。 例如,即使它们位于不同的块中,也使用相同的名称指定两个静态变量声明是无效的。

隐式本地声明

除了本地声明语句外,还可以使用隐式声明局部变量。 使用不解析为其他名称的名称的简单名称表达式通过该名称声明局部变量。 例如:

Option Explicit Off

Module Test
    Sub Main()
        x = 10
        y = 20
        Console.WriteLine(x + y)
    End Sub
End Module

隐式本地声明仅在可以接受分类为变量的表达式上下文中发生。 此规则的例外情况是,当局部变量是函数调用表达式、索引表达式或成员访问表达式的目标时,它可能不会隐式声明。

隐式局部变量被视为在包含方法的开头声明它们。 因此,它们始终限定为整个方法主体,即使声明在 lambda 表达式中也是如此。 例如,以下代码:

Option Explicit Off 

Module Test
    Sub Main()
        Dim x = Sub()
                    a = 10
                End Sub
        Dim y = Sub()
                    Console.WriteLine(a)
                End Sub

        x()
        y()
    End Sub
End Module

将打印值 10。 隐式局部变量的类型就像没有将类型字符附加到变量名称一样 Object ;否则,变量的类型是类型字符的类型。 局部变量类型推理不用于隐式局部变量。

如果显式本地声明由编译环境或自 Option Explicit变量指定,则必须显式声明所有局部变量,并且不允许隐式变量声明。

With 语句

语句 With 允许多次引用表达式的成员,而无需多次指定表达式。

WithStatement
    : 'With' Expression StatementTerminator
      Block?
      'End' 'With' StatementTerminator
    ;

表达式必须分类为一个值,并在进入块时计算一次。 在语句块中 With ,以句点或感叹号开头的成员访问表达式或字典访问表达式的计算方式与表达式前面一 With 样。 例如:

Structure Test
    Public x As Integer

    Function F() As Integer
        Return 10
    End Function
End Structure

Module TestModule
    Sub Main()
        Dim y As Test

        With y
            .x = 10
            Console.WriteLine(.x)
            .x = .F()
        End With
    End Sub
End Module

从块外部分支到 With 语句块无效。

SyncLock 语句

语句 SyncLock 允许在表达式上同步语句,这可确保执行多个线程不会同时执行相同的语句。

SyncLockStatement
    : 'SyncLock' Expression StatementTerminator
      Block?
      'End' 'SyncLock' StatementTerminator
    ;

表达式必须分类为值,并在进入块时计算一次。 输入 SyncLock 块时,对 Shared 指定的表达式调用该方法 System.Threading.Monitor.Enter ,该方法会阻塞,直到执行线程对表达式返回的对象具有排他锁。 语句中的 SyncLock 表达式类型必须是引用类型。 例如:

Class Test
    Private count As Integer = 0

    Public Function Add() As Integer
        SyncLock Me
            count += 1
            Add = count
        End SyncLock
    End Function

    Public Function Subtract() As Integer
        SyncLock Me
            count -= 1
            Subtract = count
        End SyncLock
    End Function
End Class

上面的示例在类 Test 的特定实例上同步,以确保特定实例一次不能添加或减去计数变量中的多个线程。

SyncLock由一个Try语句隐式包含,该语句的块调用SharedFinally表达式上的方法System.Threading.Monitor.Exit。 这可确保即使在引发异常时也能释放锁。 因此,从块外部分支到 SyncLock 块中无效,并且 SyncLock 块被视为一个语句,目的是 ResumeResume Next。 上面的示例等效于以下代码:

Class Test
    Private count As Integer = 0

    Public Function Add() As Integer
        Try
            System.Threading.Monitor.Enter(Me)

            count += 1
            Add = count
        Finally
            System.Threading.Monitor.Exit(Me)
        End Try
    End Function

    Public Function Subtract() As Integer
        Try
            System.Threading.Monitor.Enter(Me)

            count -= 1
            Subtract = count
        Finally
            System.Threading.Monitor.Exit(Me)
        End Try
    End Function
End Class

事件语句

RaiseEventAddHandlerRemoveHandler语句会动态引发事件并处理事件。

EventStatement
    : RaiseEventStatement
    | AddHandlerStatement
    | RemoveHandlerStatement
    ;

RaiseEvent 语句

语句 RaiseEvent 通知事件处理程序发生了特定事件。

RaiseEventStatement
    : 'RaiseEvent' IdentifierOrKeyword
      ( OpenParenthesis ArgumentList? CloseParenthesis )? StatementTerminator
    ;

语句中的RaiseEvent简单名称表达式被解释为成员查找 。Me 因此,RaiseEvent x被解释为好像它。RaiseEvent Me.x 表达式的结果必须分类为类本身中定义的事件的事件访问;不能在语句中使用 RaiseEvent 基类型上定义的事件。

RaiseEvent 语句使用提供的参数(如果有)作为对 Invoke 事件委托方法的调用进行处理。 如果委托的值为 Nothing,则不会引发异常。 如果没有参数,可以省略括号。 例如:

Class Raiser
    Public Event E1(Count As Integer)

    Public Sub Raise()
        Static RaiseCount As Integer = 0

        RaiseCount += 1
        RaiseEvent E1(RaiseCount)
    End Sub
End Class

Module Test
    Private WithEvents x As Raiser

    Private Sub E1Handler(Count As Integer) Handles x.E1
        Console.WriteLine("Raise #" & Count)
    End Sub

    Public Sub Main()
        x = New Raiser
        x.Raise()        ' Prints "Raise #1".
        x.Raise()        ' Prints "Raise #2".
        x.Raise()        ' Prints "Raise #3".
    End Sub
End Module

上面的类 Raiser 等效于:

Class Raiser
    Public Event E1(Count As Integer)

    Public Sub Raise()
        Static RaiseCount As Integer = 0
        Dim TemporaryDelegate As E1EventHandler

        RaiseCount += 1

        ' Use a temporary to avoid a race condition.
        TemporaryDelegate = E1Event
        If Not TemporaryDelegate Is Nothing Then
            TemporaryDelegate.Invoke(RaiseCount)
        End If
    End Sub
End Class

AddHandler 和 RemoveHandler 语句

尽管大多数事件处理程序通过变量自动挂钩 WithEvents ,但可能需要在运行时动态添加和删除事件处理程序。 AddHandlerRemoveHandler 语句执行此作。

AddHandlerStatement
    : 'AddHandler' Expression Comma Expression StatementTerminator
    ;

RemoveHandlerStatement
    : 'RemoveHandler' Expression Comma Expression StatementTerminator
    ;

每个语句采用两个参数:第一个参数必须是分类为事件访问的表达式,第二个参数必须是一个分类为值的表达式。 第二个参数的类型必须是与事件访问关联的委托类型。 例如:

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

给定事件 E, ,该语句将调用实例上的相关 add_Eremove_E 方法,以添加或删除委托作为事件的处理程序。 因此,上述代码等效于:

Public Class Form1
    Public Sub New()
        Button1.add_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()
        Button1.remove_Click(AddressOf Button1_Click)
    End Sub
End Class

赋值语句

赋值语句将表达式的值赋给变量。 有多种类型的工作分配。

AssignmentStatement
    : RegularAssignmentStatement
    | CompoundAssignmentStatement
    | MidAssignmentStatement
    ;

常规赋值语句

简单的赋值语句将表达式的结果存储在变量中。

RegularAssignmentStatement
    : Expression Equals Expression StatementTerminator
    ;

赋值运算符左侧的表达式必须分类为变量或属性访问,而赋值运算符右侧的表达式必须分类为值。 表达式的类型必须隐式转换为变量或属性访问的类型。

如果分配给的变量是引用类型的数组元素,将执行运行时检查以确保表达式与 array-element 类型兼容。 在下面的示例中,最后一个赋值会导致引发, System.ArrayTypeMismatchException 因为无法将实例 ArrayList 存储在数组的 String 元素中。

Dim sa(10) As String
Dim oa As Object() = sa
oa(0) = Nothing         ' This is allowed.
oa(1) = "Hello"         ' This is allowed.
oa(2) = New ArrayList() ' System.ArrayTypeMismatchException is thrown.

如果赋值运算符左侧的表达式被分类为变量,则赋值语句将该值存储在变量中。 如果表达式被归类为属性访问,则赋值语句会将属性访问权限转换为对属性的访问器的调用 Set ,并将值参数替换为值参数的值。 例如:

Module Test
    Private PValue As Integer

    Public Property P As Integer
        Get
            Return PValue
        End Get

        Set (Value As Integer)
            PValue = Value
        End Set
    End Property

    Sub Main()
        ' The following two lines are equivalent.
        P = 10
        set_P(10)
    End Sub
End Module

如果变量或属性访问的目标类型为值类型,但未分类为变量,则会发生编译时错误。 例如:

Structure S
    Public F As Integer
End Structure

Class C
    Private PValue As S

    Public Property P As S
        Get
            Return PValue
        End Get

        Set (Value As S)
            PValue = Value
        End Set
    End Property
End Class

Module Test
    Sub Main()
        Dim ct As C = New C()
        Dim rt As Object = new C()

        ' Compile-time error: ct.P not classified as variable.
        ct.P.F = 10

        ' Run-time exception.
        rt.P.F = 10
    End Sub
End Module

请注意,赋值语义取决于要为其分配的变量或属性的类型。 如果要向其分配的变量是值类型,则赋值会将表达式的值复制到变量中。 如果要向其分配的变量是引用类型,则赋值会将引用(而不是值本身)复制到变量中。 如果变量的类型为 Object,则赋值语义由值的类型是值类型还是运行时引用类型确定。

注意。 对于内部类型(如 IntegerDate),引用和值赋值语义是相同的,因为类型是不可变的。 因此,语言可以自由地将装箱内部类型的引用分配用作优化。 从值的角度来看,结果相同。

由于相等字符(=)用于赋值和相等性,因此在诸如此类 x = y.ToString()情况下,简单赋值和调用语句之间存在歧义。 在所有此类情况下,赋值语句优先于相等运算符。 这意味着示例表达式被解释为 x = (y.ToString()) 而不是 (x = y).ToString()

复合赋值语句

复合赋值语句采用形式V op= E(其中op是有效的二进制运算符)。

CompoundAssignmentStatement
    : Expression CompoundBinaryOperator LineTerminator? Expression StatementTerminator
    ;

CompoundBinaryOperator
    : '^' '=' | '*' '=' | '/' '=' | '\\' '=' | '+' '=' | '-' '='
    | '&' '=' | '<' '<' '=' | '>' '>' '='
    ;

赋值运算符左侧的表达式必须分类为变量或属性访问,而赋值运算符右侧的表达式必须分类为值。 复合赋值语句等效于该语句 V = V op E ,其差异在于复合赋值运算符左侧的变量只计算一次。 以下示例演示了这一差异:

Module Test
    Function GetIndex() As Integer
        Console.WriteLine("Getting index")
        Return 1
    End Function

    Sub Main()
        Dim a(2) As Integer

        Console.WriteLine("Simple assignment")
        a(GetIndex()) = a(GetIndex()) + 1

        Console.WriteLine("Compound assignment")
        a(GetIndex()) += 1
    End Sub
End Module

a(GetIndex())表达式的计算结果为简单赋值两次,但只计算一次复合赋值,因此代码将打印:

Simple assignment
Getting index
Getting index
Compound assignment
Getting index

中间赋值语句

Mid赋值语句将字符串分配给另一个字符串。 赋值左侧的语法与调用函数 Microsoft.VisualBasic.Strings.Mid的语法相同。

MidAssignmentStatement
    : 'Mid' '$'? OpenParenthesis Expression Comma Expression
      ( Comma Expression )? CloseParenthesis Equals Expression StatementTerminator
    ;

第一个参数是赋值的目标,必须归类为变量或属性访问,其类型可隐式转换为和从 String中转换。 第二个参数是基于 1 的起始位置,对应于赋值应在目标字符串中开始的位置,并且必须归类为必须隐式转换为 Integer的值。 可选的第三个参数是要分配给目标字符串的右侧值中的字符数,必须归类为类型可隐式转换为 Integer的值。 右侧是源字符串,必须归类为类型可隐式转换为 String的值。 右侧截断为长度参数(如果指定)并从起始位置开始替换左侧字符串中的字符。 如果右侧字符串包含的字符数少于第三个参数,则仅复制右侧字符串中的字符。

以下示例显示 ab123fg

Module Test
    Sub Main()
        Dim s1 As String = "abcdefg"
        Dim s2 As String = "1234567"

        Mid$(s1, 3, 3) = s2
        Console.WriteLine(s1)
    End Sub
End Module

注意。 Mid 不是保留字。

调用语句

调用语句调用前面有可选关键字 Call的方法。 调用语句的处理方式与函数调用表达式相同,下面指出了一些差异。 调用表达式必须分类为值或 void。 调用表达式的计算产生的任何值将被丢弃。

如果省略关键字 Call ,则调用表达式必须以标识符或关键字开头,或者以 . 块内部 With 开头。 因此,例如,“Call 1.ToString()”是有效的语句,但“1.ToString()”不是。 (请注意,在表达式上下文中,调用表达式也不需要以标识符开头。例如,“Dim x = 1.ToString()”是一个有效语句。

调用语句和调用表达式之间存在另一个区别:如果调用语句包含参数列表,则始终将此作为调用的参数列表。 以下示例演示了差异:

Module Test
    Sub Main()
        Call {Function() 15}(0)
        ' error: (0) is taken as argument list, but array is not invokable

        Call ({Function() 15}(0))
        ' valid, since the invocation statement has no argument list

        Dim x = {Function() 15}(0)
        ' valid as an expression, since (0) is taken as an array-indexing

        Call f("a")
        ' error: ("a") is taken as argument list to the invocation of f

        Call f()("a")
        ' valid, since () is the argument list for the invocation of f

        Dim y = f("a")
        ' valid as an expression, since f("a") is interpreted as f()("a")
    End Sub

    Sub f() As Func(Of String,String)
        Return Function(x) x
    End Sub
End Module
InvocationStatement
    : 'Call'? InvocationExpression StatementTerminator
    ;

条件语句

条件语句允许基于在运行时计算的表达式的条件执行语句。

ConditionalStatement
    : IfStatement
    | SelectStatement
    ;

如果。。。然后。。。Else 语句

语句 If...Then...Else 是基本条件语句。

IfStatement
    : BlockIfStatement
    | LineIfThenStatement
    ;

BlockIfStatement
    : 'If' BooleanExpression 'Then'? StatementTerminator
      Block?
      ElseIfStatement*
      ElseStatement?
      'End' 'If' StatementTerminator
    ;

ElseIfStatement
    : ElseIf BooleanExpression 'Then'? StatementTerminator
      Block?
    ;

ElseStatement
    : 'Else' StatementTerminator
      Block?
    ;

LineIfThenStatement
    : 'If' BooleanExpression 'Then' Statements ( 'Else' Statements )? StatementTerminator
    ;
    
ElseIf		
	: 'ElseIf'		
	| 'Else' 'If'   
   ;

语句中的每个 If...Then...Else 表达式都必须是一个布尔表达式,根据 Section Boolean 表达式。 (注意:这不需要表达式具有布尔类型)。 如果语句中的 If 表达式为 true,则执行块括起来的 If 语句。 如果表达式为 false,则计算每个 ElseIf 表达式。 如果其中一个 ElseIf 表达式的计算结果为 true,则执行相应的块。 如果没有表达式的计算结果为 true,并且存在块 Else ,则 Else 执行该块。 块完成执行后,执行将传递到语句的 If...Then...Else 末尾。

如果If表达式是,则语句的If行版本有一组语句,并且表达式为TrueFalse可选语句集,则执行语句集。 例如:

Module Test
    Sub Main()
        Dim a As Integer = 10
        Dim b As Integer = 20

        ' Block If statement.
        If a < b Then
            a = b
        Else
            b = a
        End If

        ' Line If statement
        If a < b Then a = b Else b = a
    End Sub
End Module

If 语句的行版本与“:”不太紧密地绑定,其 Else 绑定到语法允许的词法上 If 最接近的词法版本。 例如,以下两个版本等效:

If True Then _
If True Then Console.WriteLine("a") Else Console.WriteLine("b") _
Else Console.WriteLine("c") : Console.WriteLine("d")

If True Then
    If True Then
        Console.WriteLine("a")
    Else
        Console.WriteLine("b")
    End If
    Console.WriteLine("c") : Console.WriteLine("d")
End If

除标签声明语句之外的所有语句都允许在行 If 语句(包括块语句)内。 但是,它们可能不会使用 LineTerminator 作为 StatementTerminator,但多行 lambda 表达式中除外。 例如:

' Allowed, since it uses : instead of LineTerminator to separate statements
If b Then With New String("a"(0),5) : Console.WriteLine(.Length) : End With

' Disallowed, since it uses a LineTerminator
If b then With New String("a"(0), 5)
              Console.WriteLine(.Length)
          End With

' Allowed, since it only uses LineTerminator inside a multi-line lambda
If b Then Call Sub()
                   Console.WriteLine("a")
               End Sub.Invoke()

Select Case 语句

语句 Select Case 基于表达式的值执行语句。

SelectStatement
    : 'Select' 'Case'? Expression StatementTerminator
      CaseStatement*
      CaseElseStatement?
      'End' 'Select' StatementTerminator
    ;

CaseStatement
    : 'Case' CaseClauses StatementTerminator
      Block?
    ;

CaseClauses
    : CaseClause ( Comma CaseClause )*
    ;

CaseClause
    : ( 'Is' LineTerminator? )? ComparisonOperator LineTerminator? Expression
    | Expression ( 'To' Expression )?
    ;

ComparisonOperator
    : '=' | '<' '>' | '<' | '>' | '>' '=' | '<' '='
    ;

CaseElseStatement
    : 'Case' 'Else' StatementTerminator
      Block?
    ;

表达式必须分类为值。 Select Case执行语句时,先Select计算表达式,然后Case按文本声明的顺序计算这些语句。 计算结果为True执行其块的第一Case个语句。 Case如果没有语句的计算结果True,并且存在一个Case Else语句,则会执行该块。 完成执行块后,执行将传递到语句的 Select 末尾。

不允许对下一 Case 个开关部分执行块。 这可以防止意外省略终止语句时 Case 在其他语言中发生的常见 bug 类。 下面的示例阐释了这种行为:

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

        Select Case x
            Case 5
                Console.WriteLine("x = 5")
            Case 10
                Console.WriteLine("x = 10")
            Case 20 - 10
                Console.WriteLine("x = 20 - 10")
            Case 30
                Console.WriteLine("x = 30")
        End Select
    End Sub
End Module

代码输出:

x = 10

虽然 Case 10Case 20 - 10 针对相同的值进行选择, Case 10 但会执行该值,因为它以 Case 20 - 10 文本开头。 到达下一个 Case 语句后 Select ,执行将继续执行。

Case 句可以采用两种形式。 一个窗体是可选 Is 关键字、比较运算符和表达式。 表达式转换为表达式的类型 Select ;如果表达式无法隐式转换为表达式的类型 Select ,则会发生编译时错误。 Select如果表达式为 E,则比较运算符为 Op,且Case表达式为 E1,则事例计算为 E OP E1。 运算符必须对两个表达式的类型有效;否则会发生编译时错误。

另一种形式是一个表达式,后跟关键字 To 和第二个表达式。 这两个表达式都转换为表达式的类型 Select ;如果任一表达式无法隐式转换为表达式的类型 Select ,则会发生编译时错误。 如果表达式是,则第SelectCase个表达式是E1,第二Case个表达式是E2,计算Case方式E = E1为(如果未指定)E2(E >= E1) And (E <= E2)E 运算符必须对两个表达式的类型有效:否则会发生编译时错误。

循环语句

循环语句允许在其正文中重复执行语句。

LoopStatement
    : WhileStatement
    | DoLoopStatement
    | ForStatement
    | ForEachStatement
    ;

每次输入循环正文时,都会对该正文中声明的所有局部变量进行新的复制,并初始化为变量的先前值。 对循环正文中变量的任何引用都将使用最近创建的副本。 此代码显示了一个示例:

Module Test
    Sub Main()
        Dim lambdas As New List(Of Action)
        Dim x = 1

        For i = 1 To 3
            x = i
            Dim y = x
            lambdas.Add(Sub() Console.WriteLine(x & y))
        Next

        For Each lambda In lambdas
            lambda()
        Next
    End Sub
End Module

代码生成输出:

31    32    33

执行循环正文时,它将使用变量的当前副本。 例如,该语句Dim y = x引用最新副本和原始副本yx。 创建 lambda 时,它会记住在创建变量时当前变量的副本。 因此,每个 lambda 使用相同的共享副本 x,但使用不同的副本 y。 在程序结束时,当它执行 lambda 时,它们所引用的 x 共享副本现在为其最终值 3。

请注意,如果没有 lambda 或 LINQ 表达式,则无法知道新副本是在循环条目上进行的。 事实上,在这种情况下,编译器优化将避免进行副本。 另请注意,进入包含 lambda 或 LINQ 表达式的循环是非法 GoTo 的。

而。。。结束和执行...循环语句

While基于布尔表达式的循环Do或循环语句循环。

WhileStatement
    : 'While' BooleanExpression StatementTerminator
      Block?
      'End' 'While' StatementTerminator
    ;

DoLoopStatement
    : DoTopLoopStatement
    | DoBottomLoopStatement
    ;

DoTopLoopStatement
    : 'Do' ( WhileOrUntil BooleanExpression )? StatementTerminator
      Block?
      'Loop' StatementTerminator
    ;

DoBottomLoopStatement
    : 'Do' StatementTerminator
      Block?
      'Loop' WhileOrUntil BooleanExpression StatementTerminator
    ;

WhileOrUntil
    : 'While' | 'Until'
    ;

While只要布尔表达式的计算结果为 trueDo,循环语句就循环;循环语句可能包含更复杂的条件。 表达式可以放在关键字之后 Do 或关键字之后 Loop ,但不能放在两者之后。 布尔表达式按 Section 布尔表达式计算。 (注意:这不需要表达式具有布尔类型)。 也有效,根本不指定任何表达式;在这种情况下,循环永远不会退出。 如果表达式位于之后 Do,则会在每次迭代上执行循环块之前对其进行计算。 如果表达式位于之后 Loop,将在循环块在每个迭代上执行后计算该表达式。 因此,将表达式放在之后 Loop 将生成一个循环,而不是放置之后 Do。 下例演示此行为:

Module Test
    Sub Main()
        Dim x As Integer

        x = 3
        Do While x = 1
            Console.WriteLine("First loop")
        Loop

        Do
            Console.WriteLine("Second loop")
        Loop While x = 1
    End Sub
End Module

代码生成输出:

Second Loop

在第一个循环的情况下,将在循环执行之前评估条件。 对于第二个循环,条件在执行循环后执行。 条件表达式必须以 While 关键字或 Until 关键字为前缀。 如果条件的计算结果为 false,则前者会中断循环,后者在条件的计算结果为 true 时。

注意。 Until 不是保留字。

为。。。Next 语句

For...Next基于一组边界的语句循环。 语句 For 指定循环控件变量、下限表达式、上限表达式和可选步骤值表达式。 循环控件变量是通过标识符指定的,后跟可选 As 子句或表达式。

ForStatement
    : 'For' LoopControlVariable Equals Expression 'To' Expression
      ( 'Step' Expression )? StatementTerminator
      Block?
      ( 'Next' NextExpressionList? StatementTerminator )?
    ;

LoopControlVariable
    : Identifier ( IdentifierModifiers 'As' TypeName )?
    | Expression
    ;

NextExpressionList
    : Expression ( Comma Expression )*
    ;

根据以下规则,循环控制变量引用特定于此 For...Next 语句的新局部变量,或引用预先存在的变量或表达式。

  • 如果循环控制变量是具有 As 子句的标识符,则标识符将定义子句中指定的 As 类型的新局部变量,范围限定为整个 For 循环。

  • 如果循环控制变量是没有 As 子句的标识符,则首先使用简单名称解析规则(请参阅“节 简单名称表达式”)解析标识符,但标识符的出现本身不会导致创建隐式局部变量(Section Implicit Local Declarations)。

    • 如果此解析成功,结果被归类为变量,则循环控制变量是预先存在的变量。

    • 如果解析失败,或者解析成功并且结果被归类为类型,则:

      • 如果使用局部变量类型推理,标识符将定义一个新的局部变量,该变量的类型是从绑定表达式和步骤表达式推断的,范围限定为整个 For 循环:
      • 如果未使用局部变量类型推理,但隐式本地声明是,则会创建一个隐式局部变量,其范围是整个方法(Section Implicit Local Declarations),并且循环控制变量引用此预先存在的变量:
      • 如果未使用局部变量类型推理和隐式本地声明,则为错误。
    • 如果解析成功,则分类为既不是类型也不是变量,则这是一个错误。

  • 如果循环控制变量是表达式,则必须将表达式分类为变量。

循环控件变量不能由另一个封闭 For...Next 语句使用。 语句的循环控制变量 For 的类型决定了迭代的类型,并且必须是以下类型之一:

  • ByteSByteUShortShortUIntegerIntegerULongLongDecimalSingleDouble
  • 枚举类型
  • Object
  • 具有以下运算符的类型 T ,其中 B 一种类型可用于布尔表达式:
Public Shared Operator >= (op1 As T, op2 As T) As B
Public Shared Operator <= (op1 As T, op2 As T) As B
Public Shared Operator - (op1 As T, op2 As T) As T
Public Shared Operator + (op1 As T, op2 As T) As T

绑定表达式和步骤表达式必须隐式转换为循环控制变量的类型,并且必须归类为值。 在编译时,循环控件变量的类型是通过在下限、上限和步骤表达式类型中选择最宽的类型来推断的。 如果两种类型之间没有扩大转换,则会发生编译时错误。

在运行时,如果循环控制变量的类型为 Object,则迭代的类型与在编译时推断相同,但有两个例外。 首先,如果绑定表达式和步骤表达式都是整型类型,但没有最宽的类型,则会推断包含所有三种类型的最宽类型。 其次,如果推断 String出循环控制变量的类型, Double 将改为推断。 如果在运行时无法确定循环控件类型,或者无法将任何表达式转换为循环控件类型,将发生一个 System.InvalidCastException 。 在循环开头选择循环控件类型后,无论对循环控件变量中的值进行更改,都会在整个迭代中使用同一类型。

语句 For 必须由匹配 Next 语句关闭。 Next没有变量的语句与最内部的 open For 语句匹配,而Next具有一个或多个循环控件变量的语句将从左到右匹配匹配每个变量的For循环。 如果变量与当时不是最嵌套循环的循环匹配 For ,则编译时错误结果。

在循环的开头,三个表达式按文本顺序计算,下限表达式分配给循环控制变量。 如果省略步骤值,则它隐 1式转换为循环控制变量的类型。 这三个表达式仅在循环的开头计算。

在每个循环的开头,将比较控制变量,以确定它是否大于终点(如果步骤表达式为正数)或小于终点(如果步骤表达式为负)。 如果是,循环 For 将终止;否则将执行循环块。 如果循环控制变量不是基元类型,则比较运算符由表达式 step >= step - step 是 true 还是 false 确定。 在 Next 语句中,步骤值将添加到控制变量,执行将返回到循环的顶部。

请注意,循环控制变量的新副本 不会 在循环块的每个迭代上创建。 在这方面,该 For 语句不同于 For Each每个部分...Next 语句)。

从循环外部分支到 For 循环无效。

对于每个...Next 语句

For Each...Next语句基于表达式中的元素循环。 语句 For Each 指定循环控件变量和枚举器表达式。 循环控件变量是通过标识符指定的,后跟可选 As 子句或表达式。

ForEachStatement
    : 'For' 'Each' LoopControlVariable 'In' LineTerminator? Expression StatementTerminator
      Block?
      ( 'Next' NextExpressionList? StatementTerminator )?
    ;

遵循与语句相同的规则 For...Next (Section For...下一个语句),循环控制变量引用特定于此 For Each... 的新局部变量...下一个语句或预先存在的变量或表达式。

枚举器表达式必须分类为值,其类型必须是集合类型或 Object。 如果枚举器表达式的类型为 Object,则所有处理都会延迟到运行时。 否则,必须存在从集合的元素类型到循环控件变量类型的转换。

循环控制变量不能由另一个封闭 For Each 语句使用。 语句 For Each 必须由匹配 Next 语句关闭。 Next没有循环控件变量的语句与最内部的打开For Each项匹配。 Next具有一个或多个循环控件变量的语句将从左到右匹配For Each具有相同循环控制变量的循环。 如果变量与当时不是最嵌套循环的循环匹配 For Each ,则会发生编译时错误。

C如果为以下类型之一,则类型为集合类型

  • 以下所有内容均为 true:

    • C包含具有返回类型的E签名GetEnumerator()的可访问实例、共享或扩展方法。
    • E 包含具有签名 MoveNext() 和返回类型的 Boolean可访问实例、共享或扩展方法。
    • E 包含具有 Current getter 的可访问实例或共享属性。 此属性的类型是集合类型的元素类型。
  • 它实现接口 System.Collections.Generic.IEnumerable(Of T),在这种情况下,集合的元素类型被视为 T

  • 它实现接口 System.Collections.IEnumerable,在这种情况下,集合的元素类型被视为 Object

下面是可以枚举的类的示例:

Public Class IntegerCollection
    Private integers(10) As Integer

    Public Class IntegerCollectionEnumerator
        Private collection As IntegerCollection
        Private index As Integer = -1

        Friend Sub New(c As IntegerCollection)
            collection = c
        End Sub

        Public Function MoveNext() As Boolean
            index += 1

            Return index <= 10
        End Function

        Public ReadOnly Property Current As Integer
            Get
                If index < 0 OrElse index > 10 Then
                    Throw New System.InvalidOperationException()
                End If

                Return collection.integers(index)
            End Get
        End Property
    End Class

    Public Sub New()
        Dim i As Integer

        For i = 0 To 10
            integers(i) = I
        Next i
    End Sub

    Public Function GetEnumerator() As IntegerCollectionEnumerator
        Return New IntegerCollectionEnumerator(Me)
    End Function
End Class

在循环开始之前,将计算枚举器表达式。 如果表达式的类型不满足设计模式,则表达式将强制转换为或System.Collections.Generic.IEnumerable(Of T)转换System.Collections.IEnumerable。 如果表达式类型实现泛型接口,则泛型接口在编译时优先,但在运行时首选非泛型接口。 如果表达式类型多次实现泛型接口,则语句被视为不明确且发生编译时错误。

注意。 非泛型接口在后期绑定的情况下是首选的,因为选取泛型接口意味着对接口方法的所有调用都涉及类型参数。 由于在运行时无法知道匹配的类型参数,因此所有此类调用都必须使用后期绑定调用进行。 这比调用非泛型接口慢,因为可以使用编译时调用来调用非泛型接口。

GetEnumerator 对生成的值调用,函数的返回值存储在临时值中。 然后在每次迭代开始时, MoveNext 对临时调用。 如果返回 False,循环将终止。 否则,将按如下所示执行循环的每个迭代:

  1. 如果循环控制变量标识了新的局部变量(而不是预先存在的局部变量),则会创建此局部变量的新副本。 对于当前迭代,循环块中的所有引用都将引用此副本。
  2. 检索 Current 属性,强制转换为循环控件变量的类型(无论转换是隐式转换还是显式转换),并分配给循环控件变量。
  3. 循环块执行。

注意。 语言版本 10.0 和 11.0 之间的行为略有变化。 在 11.0 之前, 不会 为循环的每个迭代创建一个新的迭代变量。 仅当迭代变量由 lambda 或 LINQ 表达式捕获时,才能观察到此差异,该表达式随后在循环后调用:

Dim lambdas As New List(Of Action)
For Each x In {1,2,3}
   lambdas.Add(Sub() Console.WriteLine(x)
Next
lambdas(0).Invoke()
lambdas(1).Invoke()
lambdas(2).Invoke()

最长为 Visual Basic 10.0,这在编译时生成了警告,并打印了“3”三次。 这是因为循环的所有迭代只共享了单个变量“x”,并且所有三个 lambda 捕获了相同的“x”,并且执行 lambda 时,该变量将保留数字 3。 从 Visual Basic 11.0 起,它打印“1、2、3”。 这是因为每个 lambda 捕获不同的变量“x”。

注意。 迭代的当前元素将转换为循环控制变量的类型,即使转换是显式的,因为语句中没有引入转换运算符的方便位置。 使用现在过时的类型 System.Collections.ArrayList时,这变得特别麻烦,因为它的元素类型是 Object。 这将需要演员在很大的很多循环,我们觉得这是不理想的。 具有讽刺意味的是,泛型实现了强类型集合的创建, System.Collections.Generic.List(Of T)这可能会让我们重新思考这个设计点,但为了兼容,现在无法更改。

Next达到该语句后,执行将返回到循环的顶部。 如果在关键字之后 Next 指定了变量,则它必须与关键字后面的 For Each第一个变量相同。 例如,考虑以下代码:

Module Test
    Sub Main()
        Dim i As Integer
        Dim c As IntegerCollection = New IntegerCollection()

        For Each i In c
            Console.WriteLine(i)
        Next i
    End Sub
End Module

它等效于以下代码:

Module Test
    Sub Main()
        Dim i As Integer
        Dim c As IntegerCollection = New IntegerCollection()

        Dim e As IntegerCollection.IntegerCollectionEnumerator

        e = c.GetEnumerator()
        While e.MoveNext()
            i = e.Current

            Console.WriteLine(i)
        End While
    End Sub
End Module

如果枚举器的类型 E 实现 System.IDisposable,则通过调用 Dispose 该方法退出循环时释放枚举器。 这可确保释放枚举器持有的资源。 如果包含 For Each 该语句的方法不使用非结构化错误处理,则 For Each 语句包装在语句中 Try ,并 Dispose 调用 Finally 该方法以确保清理。

注意。System.Array 类型是集合类型,由于所有数组类型都派生自 System.Array,因此允许在语句中使用 For Each 任何数组类型表达式。 对于单维数组,该 For Each 语句以递增索引顺序枚举数组元素,从索引 0 开始,以索引长度 - 1 结尾。 对于多维数组,最右侧维度的索引首先增加。

例如,以下代码打印 1 2 3 4

Module Test
    Sub Main()
        Dim x(,) As Integer = { { 1, 2 }, { 3, 4 } }
        Dim i As Integer

        For Each i In x
            Console.Write(i & " ")
        Next i
    End Sub
End Module

从块外部分支到 For Each 语句块无效。

Exception-Handling 语句

Visual Basic 支持结构化异常处理和非结构化异常处理。 方法中只能使用一种异常处理样式,但 Error 语句可用于结构化异常处理。 如果方法使用这两种异常处理样式,则编译时错误结果。

ErrorHandlingStatement
    : StructuredErrorStatement
    | UnstructuredErrorStatement
    ;

结构化 Exception-Handling 语句

结构化异常处理是一种通过声明显式块来处理错误的方法,在该块中将处理某些异常。 结构化异常处理是通过 Try 语句完成的。

StructuredErrorStatement
    : ThrowStatement
    | TryStatement
    ;

TryStatement
    : 'Try' StatementTerminator
      Block?
      CatchStatement*
      FinallyStatement?
      'End' 'Try' StatementTerminator
    ;

例如:

Module Test
    Sub ThrowException()
        Throw New Exception()
    End Sub

    Sub Main()
        Try
            ThrowException()
        Catch e As Exception
            Console.WriteLine("Caught exception!")
        Finally
            Console.WriteLine("Exiting try.")
        End Try
    End Sub
End Module

语句 Try 由三种类型的块组成:try 块、catch 块和最后块。 try 块是包含要执行的语句的语句块。 catch 块是处理异常的语句块。 最后一个块是一个语句块,其中包含在退出语句时Try要运行的语句,而不考虑是否已发生异常并已处理。 一个 Try 语句,只能包含一个 try 块和一个最终块,必须至少包含一个 catch 块或最终块。 除非在同一语句的 catch 块中,否则显式将执行显式传输到 try 块中无效。

Finally 块

Finally执行离开语句的任何部分Try时,始终执行块。 执行该块不需要显式作 Finally ;执行离开 Try 语句时,系统将自动执行 Finally 该块,然后将执行传输到其预期目标。 无论 Finally 执行如何离开 Try 语句,都会执行该块:通过块的 Try 末尾、块的 Catch 末尾、通过 Exit Try 语句、 GoTo 通过语句或通过不处理引发的异常来执行该块。

请注意, Await 异步方法中的表达式和 Yield 迭代器方法中的语句可能导致控制流在异步或迭代器方法实例中挂起,并在其他一些方法实例中恢复。 但是,这只是执行挂起,不涉及退出相应的异步方法或迭代器方法实例,因此不会导致 Finally 执行块。

将执行显式传输到块无效;除了通过异常将执行 Finally 从块中传输出去 Finally ,也无效。

FinallyStatement
    : 'Finally' StatementTerminator
      Block?
    ;

catch 块

如果在处理 Try 块时发生异常,则会按文本顺序检查每个 Catch 语句,以确定它是否处理异常。

CatchStatement
    : 'Catch' ( Identifier ( 'As' NonArrayTypeName )? )?
	  ( 'When' BooleanExpression )? StatementTerminator
      Block?
    ;

子句中指定的 Catch 标识符表示已引发的异常。 如果标识符包含子 As 句,则标识符被视为在块的本地声明空间中 Catch 声明。 否则,标识符必须是在包含块中定义的局部变量(而不是静态变量)。

Catch没有标识符的子句将捕获派生自System.Exception的所有异常。 Catch具有标识符的子句将只捕获其类型与标识符类型相同或派生的异常。 类型必须是 System.Exception派生自 System.Exception的类型。 当捕获派生自 System.Exception的异常时,对异常对象的引用存储在函数 Microsoft.VisualBasic.Information.Err返回的对象中。

Catch当表达式的计算结果为True时,具有子When句的子句将仅捕获异常;表达式的类型必须是一个布尔表达式(根据 Section Boolean 表达式)。 子 When 句仅在检查异常类型后应用,表达式可能引用表示异常的标识符,如以下示例所示:

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

        Try
            Throw New ArgumentException()
        Catch e As OverflowException When i = 5
            Console.WriteLine("First handler")
        Catch e As ArgumentException When i = 4
            Console.WriteLine("Second handler")
        Catch When i = 5
            Console.WriteLine("Third handler")
        End Try

    End Sub
End Module

此示例打印:

Third handler

如果子 Catch 句处理异常,则执行将传输到 Catch 块。 在 Catch 块的末尾,执行将传输到语句后面的 Try 第一个语句。 该 Try 语句不会处理在块中 Catch 引发的任何异常。 Catch如果没有子句处理异常,则执行将传输到系统确定的位置。

将执行 Catch 显式传输到块无效。

在引发异常之前,通常会计算 When 子句中的筛选器。 例如,以下代码将打印“Filter, Finally, Catch”。

Sub Main()
   Try
       Foo()
   Catch ex As Exception When F()
       Console.WriteLine("Catch")
   End Try
End Sub

Sub Foo()
    Try
        Throw New Exception
    Finally
        Console.WriteLine("Finally")
    End Try
End Sub

Function F() As Boolean
    Console.WriteLine("Filter")
    Return True
End Function

但是,异步和迭代器方法会导致在外部的任何筛选器之前执行它们中的所有最终块。 例如,如果上述代码具有 Async Sub Foo(),则输出将为“Finally, Filter, Catch”。

Throw 语句

Throw 语句引发异常,该异常由派生自 System.Exception的类型实例表示。

ThrowStatement
    : 'Throw' Expression? StatementTerminator
    ;

如果表达式未分类为值或不是派生自 System.Exception的类型,则会发生编译时错误。 如果表达式在运行时的计算结果为 null 值,则会 System.NullReferenceException 改为引发异常。

只要没有干预最后的块,语句 Throw 可能会省略语句的 Try catch 块中的表达式。 在这种情况下,该语句将重新引发当前在 catch 块中处理的异常。 例如:

Sub Test(x As Integer)
    Try
        Throw New Exception()
    Catch
        If x = 0 Then
            Throw    ' OK, rethrows exception from above.
        Else
            Try
                If x = 1 Then
                    Throw   ' OK, rethrows exception from above.
                End If
            Finally
                Throw    ' Invalid, inside of a Finally.
            End Try
        End If
    End Try
End Sub

非结构化 Exception-Handling 语句

非结构化异常处理是一种处理错误的方法,方法是将语句指示到发生异常时的分支。 非结构化异常处理使用三个语句实现: Error 语句、 On Error 语句和 Resume 语句。

UnstructuredErrorStatement
    : ErrorStatement
    | OnErrorStatement
    | ResumeStatement
    ;

例如:

Module Test
    Sub ThrowException()
        Error 5
    End Sub

    Sub Main()
        On Error GoTo GotException

        ThrowException()
        Exit Sub

GotException:
        Console.WriteLine("Caught exception!")
        Resume Next
    End Sub
End Module

当方法使用非结构化异常处理时,将为捕获所有异常的整个方法建立单个结构化异常处理程序。 (请注意,在构造函数中,此处理程序不会通过对构造函数开头调用的调用 New 进行扩展。然后,该方法跟踪最近引发的异常处理程序位置和最近的异常。 在方法的条目中,异常处理程序位置和异常都设置为 Nothing。 在使用非结构化异常处理的方法中引发异常时,对异常对象的引用存储在函数 Microsoft.VisualBasic.Information.Err返回的对象中。

迭代器或异步方法中不允许使用非结构化错误处理语句。

Error 语句

语句 Error 引发包含 System.Exception Visual Basic 6 异常号的异常。 表达式必须分类为值,并且其类型必须隐式转换为 Integer

ErrorStatement
    : 'Error' Expression StatementTerminator
    ;

On Error 语句

语句 On Error 修改最新的异常处理状态。

OnErrorStatement
    : 'On' 'Error' ErrorClause StatementTerminator
    ;

ErrorClause
    : 'GoTo' '-' '1'
    | 'GoTo' '0'
    | GoToStatement
    | 'Resume' 'Next'
    ;

它可通过以下四种方式之一使用:

  • On Error GoTo -1 将最近的异常重置为 Nothing

  • On Error GoTo 0 将最新的异常处理程序位置重置为 Nothing

  • On Error GoTo LabelName 将标签建立为最新的异常处理程序位置。 此语句不能用于包含 lambda 或查询表达式的方法。

  • On Error Resume NextResume Next 行为建立为最新的异常处理程序位置。

Resume 语句

语句 Resume 将执行返回到导致最新异常的语句。

ResumeStatement
    : 'Resume' ResumeClause? StatementTerminator
    ;

ResumeClause
    : 'Next'
    | LabelName
    ;

Next如果指定了修饰符,则执行将返回到在导致最新异常的语句之后执行的语句。 如果指定了标签名称,则执行将返回到标签。

由于该 SyncLock 语句包含隐式结构化错误处理块, Resume 并且 Resume Next 对语句中 SyncLock 发生的异常具有特殊行为。 Resume 将执行返回到语句的 SyncLock 开头,同时 Resume Next 将执行返回到语句后面的 SyncLock 下一个语句。 例如,考虑以下代码:

Class LockClass
End Class

Module Test
    Sub Main()
        Dim FirstTime As Boolean = True
        Dim Lock As LockClass = New LockClass()

        On Error GoTo Handler

        SyncLock Lock
            Console.WriteLine("Before exception")
            Throw New Exception()
            Console.WriteLine("After exception")
        End SyncLock

        Console.WriteLine("After SyncLock")
        Exit Sub

Handler:
        If FirstTime Then
            FirstTime = False
            Resume
        Else
            Resume Next
        End If
    End Sub
End Module

它输出以下结果。

Before exception
Before exception
After SyncLock

第一次通过 SyncLock 语句, Resume 将执行返回到语句的 SyncLock 开头。 第二次通过 SyncLock 语句, Resume Next 将执行返回到语句的 SyncLock 末尾。 ResumeResume Next 不允许在语句中 SyncLock

在所有情况下,执行语句时 Resume ,最新的异常设置为 NothingResume如果执行语句时没有最新的异常,该语句将引发一个System.Exception包含 Visual Basic 错误号20的异常(恢复且没有错误)。

Branch 语句

分支语句修改方法中的执行流。 有六个分支语句:

  1. 语句 GoTo 会导致执行传输到方法中的指定标签。 如果 lambda 或 LINQ 表达式中捕获该块的局部变量,则不允许它进入 TryUsingSyncLockWithForFor Each块,也不允许GoTo进入任何循环块。
  2. 语句 Exit 在紧接包含指定类型的块语句的末尾之后,将执行传输到下一个语句。 如果块是方法块,则控制流将退出方法,如节 控制流中所述。 Exit如果语句不包含在语句中指定的块类型中,则会发生编译时错误。
  3. 语句 Continue 将执行传输到立即包含指定类型的块循环语句的末尾。 Continue如果语句不包含在语句中指定的块类型中,则会发生编译时错误。
  4. 语句 Stop 导致调试器异常发生。
  5. 语句 End 终止程序。 终结器在关闭之前运行,但不会执行任何当前正在执行的 Try 语句的最后块。 此语句不能用于不可执行的程序(例如 DLL)。
  6. Return没有表达式的语句等效于或Exit SubExit Function语句。 Return表达式的语句仅允许在函数的正则方法中,或在异步方法中是一些T函数的返回类型的Task(Of T)函数。 表达式必须分类为可隐式转换为 函数返回变量 (在常规方法的情况下)或 任务返回变量 (在异步方法的情况下)的值。 其行为是计算表达式,然后将其存储在返回变量中,然后执行隐式 Exit Function 语句。
BranchStatement
    : GoToStatement
    | ExitStatement
    | ContinueStatement
    | StopStatement
    | EndStatement
    | ReturnStatement
    ;

GoToStatement
    : 'GoTo' LabelName StatementTerminator
    ;

ExitStatement
    : 'Exit' ExitKind StatementTerminator
    ;

ExitKind
    : 'Do' | 'For' | 'While' | 'Select' | 'Sub' | 'Function' | 'Property' | 'Try'
    ;

ContinueStatement
    : 'Continue' ContinueKind StatementTerminator
    ;

ContinueKind
    : 'Do' | 'For' | 'While'
    ;

StopStatement
    : 'Stop' StatementTerminator
    ;

EndStatement
    : 'End' StatementTerminator
    ;

ReturnStatement
    : 'Return' Expression? StatementTerminator
    ;

Array-Handling 语句

两个语句简化了对数组的处理: ReDim 语句和 Erase 语句。

ArrayHandlingStatement
    : RedimStatement
    | EraseStatement
    ;

ReDim 语句

语句 ReDim 实例化新数组。

RedimStatement
    : 'ReDim' 'Preserve'? RedimClauses StatementTerminator
    ;

RedimClauses
    : RedimClause ( Comma RedimClause )*
    ;

RedimClause
    : Expression ArraySizeInitializationModifier
    ;

语句中的每个子句都必须分类为变量或属性访问,其类型为数组类型, Object或后跟数组边界列表。 边界数必须与变量的类型一致;允许任意数量的边界。Object 在运行时,使用指定的边界为每个表达式实例化数组,然后分配给变量或属性。 如果变量类型为 Object,则维度数是指定的维度数,数组元素类型为 Object。 如果在运行时给定的维度数与变量或属性不兼容,则会发生编译时错误。 例如:

Module Test
    Sub Main()
        Dim o As Object
        Dim b() As Byte
        Dim i(,) As Integer

        ' The next two statements are equivalent.
        ReDim o(10,30)
        o = New Object(10, 30) {}

        ' The next two statements are equivalent.
        ReDim b(10)
        b = New Byte(10) {}

        ' Error: Incorrect number of dimensions.
        ReDim i(10, 30, 40)
    End Sub
End Module

Preserve如果指定了关键字,则表达式还必须作为值进行分类,并且每个维度的新大小必须与现有数组的大小相同。 现有数组中的值将复制到新数组中:如果新数组较小,则会丢弃现有值;如果新数组更大,则额外元素将初始化为数组的元素类型的默认值。 例如,考虑以下代码:

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

        x(3, 3) = 3

        ReDim Preserve x(5, 6)
        Console.WriteLine(x(3, 3) & ", " & x(3, 6))
    End Sub
End Module

它输出以下结果:

3, 0

如果现有数组引用在运行时为 null 值,则不会给出任何错误。 除了最右侧的维度之外,如果维度的大小发生更改,将引发一个 System.ArrayTypeMismatchException

注意。 Preserve 不是保留字。

Erase 语句

语句 Erase 将语句中指定的每个数组变量或属性设置为 Nothing。 语句中的每个表达式都必须分类为变量或属性访问,其类型为数组类型或 Object。 例如:

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

        ' The following two statements are equivalent.
        Erase x
        x = Nothing
    End Sub
End Module
EraseStatement
    : 'Erase' EraseExpressions StatementTerminator
    ;

EraseExpressions
    : Expression ( Comma Expression )*
    ;

Using 语句

运行回收时,垃圾回收器会自动释放类型的实例,并且找不到对该实例的实时引用。 如果类型持有特别有价值的稀缺资源(如数据库连接或文件句柄),则可能需要等到下一个垃圾回收来清理不再使用的类型的特定实例。 若要提供在集合之前释放资源的轻量方式,类型可以实现 System.IDisposable 接口。 这样做的类型公开了 Dispose 可以调用的方法,以强制立即释放有价值的资源,如下所示:

Module Test
    Sub Main()
        Dim x As DBConnection = New DBConnection("...")

        ' Do some work
        ...

        x.Dispose()        ' Free the connection
    End Sub
End Module

Using 语句自动执行获取资源、执行一组语句,然后释放资源的过程。 该语句可以采用两种形式:在一种中,资源是声明为语句的一部分并被视为常规局部变量声明语句的局部变量;另一个资源是表达式的结果。

UsingStatement
    : 'Using' UsingResources StatementTerminator
      Block?
      'End' 'Using' StatementTerminator
    ;

UsingResources
    : VariableDeclarators
    | Expression
    ;

如果资源是局部变量声明语句,则局部变量声明的类型必须是可隐式转换为 System.IDisposable的类型。 声明的局部变量是只读的,范围限定为 Using 语句块,必须包含初始值设定项。 如果资源是表达式的结果,则必须将表达式分类为值,并且必须属于可隐式转换为 System.IDisposable的类型。 表达式仅在语句开头计算一次。

UsingTry 最终阻止对资源调用方法 IDisposable.Dispose 的语句隐式包含。 这可确保即使引发异常,也会释放资源。 因此,从块外部分支到 Using 块中无效,并且 Using 块被视为一个语句,目的是 ResumeResume Next。 如果资源是 Nothing,则不调用该资源 Dispose 。 因此,示例:

Using f As C = New C()
    ...
End Using

等效于:

Dim f As C = New C()
Try
    ...
Finally
    If f IsNot Nothing Then
        f.Dispose()
    End If
End Try

Using具有局部变量声明语句的语句一次可以获取多个资源,这相当于嵌套Using语句。 例如, Using 窗体的语句:

Using r1 As R = New R(), r2 As R = New R()
    r1.F()
    r2.F()
End Using

等效于:

Using r1 As R = New R()
    Using r2 As R = New R()
        r1.F()
        r2.F()
    End Using
End Using

Await 语句

await 语句的语法与 await 运算符表达式(Section Await 运算符)相同,仅在允许 await 表达式的方法中允许,并且具有与 await 运算符表达式相同的行为。

但是,它可以归类为值或 void。 等待运算符表达式的计算产生的任何值将被丢弃。

AwaitStatement
    : AwaitOperatorExpression StatementTerminator
    ;

Yield 语句

Yield 语句与迭代器方法相关,如 Section Iterator 方法中所述。

YieldStatement
    : 'Yield' Expression StatementTerminator
    ;

Yield 是保留字,如果立即封闭的方法或 lambda 表达式在其中显示有 Iterator 修饰符,如果 Yield 出现在该 Iterator 修饰符之后,则为保留字;它在别处未保留。 预处理器指令中也未保留它。 yield 语句仅在方法或 lambda 表达式的正文中允许,其中它是保留字。 在立即封闭的方法或 lambda 中,yield 语句可能不会出现在语句CatchFinally的主体内,也可能不会出现在语句的SyncLock主体内。

yield 语句采用一个表达式,该表达式必须分类为值,其类型可隐式转换为其封闭迭代器方法的 迭代器当前变量 (Section Iterator Methods)的类型。

仅当在MoveNext迭代器对象上调用方法时,控制流才到达Yield语句。 (这是因为迭代器方法实例只执行其语句,因为 MoveNext 对迭代器对象调用的方法或 Dispose 方法;并且 Dispose 该方法将只执行块中的 Finally 代码,其中 Yield 不允许)。

Yield执行语句时,其表达式将计算并存储在与该迭代器对象关联的迭代器方法实例的迭代器当前变量中。 该值 True 将返回到调用方 MoveNext,并且此实例的控制点将停止前进,直到迭代器对象的下一次调用 MoveNext 为止。