Partilhar via


Membros do tipo

Os membros do tipo definem locais de armazenamento e código executável. Eles podem ser métodos, construtores, eventos, constantes, variáveis e propriedades.

Implementação do método de interface

Métodos, eventos e propriedades podem implementar membros da interface. Para implementar um membro da interface, uma declaração de membro especifica a palavra-chave e lista um ou mais membros da Implements interface.

ImplementsClause
    : ( 'Implements' ImplementsList )?
    ;

ImplementsList
    : InterfaceMemberSpecifier ( Comma InterfaceMemberSpecifier )*
    ;

InterfaceMemberSpecifier
    : NonArrayTypeName Period IdentifierOrKeyword
    ;

Os métodos e propriedades que implementam membros da interface são implicitamente NotOverridable , a menos que sejam declarados como MustOverride, Overridableou substituam outro membro. É um erro para um membro que implementa um membro da interface ser Shared. A acessibilidade de um membro não tem efeito sobre sua capacidade de implementar membros da interface.

Para que uma implementação de interface seja válida, a lista de implementos do tipo que contém deve nomear uma interface que contenha um membro compatível. Um membro compatível é aquele cuja assinatura corresponde à assinatura do membro implementador. Se uma interface genérica estiver sendo implementada, o argumento type fornecido na cláusula Implements será substituído na assinatura ao verificar a compatibilidade. Por exemplo:

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

Se um evento declarado usando um tipo de delegado estiver implementando um evento de interface, um evento compatível será aquele cujo tipo de delegado subjacente é do mesmo tipo. Caso contrário, o evento usa o tipo de delegado do evento de interface que está implementando. Se esse evento implementar vários eventos de interface, todos os eventos de interface deverão ter o mesmo tipo de delegado subjacente. Por exemplo:

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

Um membro da interface na lista de implementos é especificado usando um nome de tipo, um ponto e um identificador. O nome do tipo deve ser uma interface na lista implements ou uma interface base de uma interface na lista implements, e o identificador deve ser um membro da interface especificada. Um único membro pode implementar mais de um membro da interface correspondente.

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

Se o membro da interface que está sendo implementado não estiver disponível em todas as interfaces explicitamente implementadas devido à herança de várias interfaces, o membro implementador deverá fazer referência explícita a uma interface base na qual o membro está disponível. Por exemplo, se I1 e I2 contiver um membro M, e I3 herdar de I1 e I2, um tipo de implementação I3 implementará I1.M e I2.M. Se uma interface sombrear membros herdados, um tipo de implementação terá que implementar os membros herdados e o(s) membro(s) sombreando-os.

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

Se a interface que contém o membro da interface a ser implementado for genérica, os mesmos argumentos de tipo que a interface que está sendo implementada devem ser fornecidos. Por exemplo:

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

Metodologia

Os métodos contêm as instruções executáveis de um programa.

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
    ;

Os métodos, que têm uma lista opcional de parâmetros e um valor de retorno opcional, são compartilhados ou não compartilhados. Os métodos compartilhados são acessados por meio da classe ou instâncias da classe. Métodos não compartilhados, também chamados de métodos de instância, são acessados por meio de instâncias da classe. O exemplo a seguir mostra uma classe Stack que tem vários métodos compartilhados (Clone e Flip), e vários métodos de instância (Push, Pop, e ToString):

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

Os métodos podem ser sobrecarregados, o que significa que vários métodos podem ter o mesmo nome, desde que tenham assinaturas exclusivas. A assinatura de um método consiste no número e tipos de seus parâmetros. A assinatura de um método especificamente não inclui o tipo de retorno ou modificadores de parâmetro, como Optional, ByRef ou ParamArray. O exemplo a seguir mostra uma classe com várias sobrecargas:

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

A saída do programa é:

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

Sobrecargas que diferem apenas em parâmetros opcionais podem ser usadas para "versionamento" de bibliotecas. Por exemplo, v1 de uma biblioteca pode incluir uma função com parâmetros opcionais:

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

Em seguida, v2 da biblioteca quer adicionar outro parâmetro opcional "senha", e quer fazê-lo sem quebrar a compatibilidade de origem (para que os aplicativos que costumavam visar v1 podem ser recompilados), e sem quebrar a compatibilidade binária (para que os aplicativos que costumavam fazer referência v1 agora podem referenciar v2 sem recompilação). É assim que a v2 ficará:

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

Observe que os parâmetros opcionais em uma API pública não são compatíveis com CLS. No entanto, eles podem ser consumidos pelo menos pelo Visual Basic e C#4 e F#.

Declarações de método regular, assíncrono e iterador

Existem dois tipos de métodos: sub-rotinas, que não retornam valores, e funções, que retornam. O corpo e End a construção de um método só podem ser omitidos se o método for definido numa interface ou tiver o MustOverride modificador. Se nenhum tipo de retorno for especificado em uma função e semântica estrita estiver sendo usada, ocorrerá um erro em tempo de compilação; caso contrário, o tipo é implicitamente Object ou o tipo do caractere de tipo do método. O domínio de acessibilidade do tipo de retorno e os tipos de parâmetros de um método devem ser iguais ou um superconjunto do domínio de acessibilidade do próprio método.

Um método regular é aquele sem modificadores AsyncIterator . Pode ser uma sub-rotina ou uma função. A seção Métodos regulares detalha o que acontece quando um método regular é invocado.

Um método iterador é um com o Iterator modificador e nenhum Async modificador. Deve ser uma função, e seu tipo de retorno deve ser IEnumerator, IEnumerableou IEnumerator(Of T)IEnumerable(Of T) para alguns T, e não deve ter ByRef parâmetros. Section Iterator Methods detalha o que acontece quando um método iterador é invocado.

Um método assíncrono é um com o Async modificador e nenhum Iterator modificador. Deve ser uma sub-rotina, ou uma função com tipo Task de retorno ou Task(Of T) para alguns T, e não deve ter ByRef parâmetros. Seção Métodos assíncronos detalha o que acontece quando um método assíncrono é invocado.

É um erro em tempo de compilação se um método não é um desses três tipos de método.

As declarações de sub-rotina e função são especiais na medida em que suas instruções de início e fim devem começar no início de uma linha lógica. Além disso, o corpo de uma declaração não-sub-rotinaMustOverride ou função deve começar no início de uma linha lógica. Por exemplo:

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

Declarações de método externo

Uma declaração de método externo introduz um novo método cuja implementação é fornecida externamente ao programa.

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
    ;

Como uma declaração de método externo não fornece nenhuma implementação real, ela não tem nenhum corpo de método ou End construção. Os métodos externos são implicitamente compartilhados, podem não ter parâmetros de tipo e não podem manipular eventos ou implementar membros da interface. Se nenhum tipo de retorno for especificado em uma função e semântica estrita estiver sendo usada, ocorrerá um erro em tempo de compilação. Caso contrário, o tipo é implicitamente Object ou o tipo do caractere de tipo do método. O domínio de acessibilidade do tipo de retorno e os tipos de parâmetros de um método externo devem ser iguais ou um superconjunto do domínio de acessibilidade do próprio método externo.

A cláusula library de uma declaração de método externo especifica o nome do arquivo externo que implementa o método. A cláusula de alias opcional é uma cadeia de caracteres que especifica o ordinal numérico (prefixado por um # caractere) ou o nome do método no arquivo externo. Um modificador de conjunto de caracteres únicos também pode ser especificado, que governa o conjunto de caracteres usado para organizar cadeias de caracteres durante uma chamada para o método externo. O Unicode modificador marshals todas as cadeias de caracteres para valores Unicode, o Ansi modificador marshals todas as cadeias de caracteres para valores ANSI e o Auto modificador marshals as cadeias de caracteres de acordo com as regras do .NET Framework com base no nome do método ou no nome do alias, se especificado. Se nenhum modificador for especificado, o padrão será Ansi.

Se Ansi ou Unicode for especificado, o nome do método será pesquisado no arquivo externo sem nenhuma modificação. Se Auto for especificado, a pesquisa do nome do método depende da plataforma. Se a plataforma for considerada ANSI (por exemplo, Windows 95, Windows 98, Windows ME), o nome do método será pesquisado sem nenhuma modificação. Se a pesquisa falhar, um A é anexado e a pesquisa tentada novamente. Se a plataforma for considerada Unicode (por exemplo, Windows NT, Windows 2000, Windows XP), então a W é anexada e o nome é pesquisado. Se a pesquisa falhar, a pesquisa será tentada novamente sem o Warquivo . Por exemplo:

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

Os tipos de dados que estão sendo passados para métodos externos são empacotados de acordo com as convenções de empacotamento de dados do .NET Framework com uma exceção. As variáveis de cadeia de caracteres que são passadas por valor (ou seja, ByVal x As String) são empacotadas para o tipo BSTR de automação OLE, e as alterações feitas no BSTR no método externo são refletidas de volta no argumento string. Isso ocorre porque o tipo String em métodos externos é mutável, e esse marshalling especial imita esse comportamento. Os parâmetros de cadeia de caracteres que são passados por referência (ou seja, ByRef x As String) são empacotados como um ponteiro para o tipo BSTR de automação OLE. É possível substituir esses comportamentos especiais especificando o System.Runtime.InteropServices.MarshalAsAttribute atributo no parâmetro.

O exemplo demonstra o uso de métodos externos:

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

Métodos substituíveis

O Overridable modificador indica que um método é substituível. O Overrides modificador indica que um método substitui um método substituível do tipo base que tem a mesma assinatura. O NotOverridable modificador indica que um método substituível não pode ser substituído. O MustOverride modificador indica que um método deve ser substituído em classes derivadas.

Certas combinações destes modificadores não são válidas:

  • Overridable e NotOverridable excluem-se mutuamente e não podem ser combinados.

  • MustOverride implica Overridable (e, portanto, não pode especificá-lo) e não pode ser combinado com NotOverridable.

  • NotOverridable não pode ser combinado com Overridable ou MustOverride e deve ser combinado com Overrides.

  • Overrides implica Overridable (e, portanto, não pode especificá-lo) e não pode ser combinado com MustOverride.

Há também restrições adicionais sobre métodos substituíveis:

  • Um MustOverride método não pode incluir um corpo de método ou uma End construção, não pode substituir outro método e só pode aparecer em MustInherit classes.

  • Se um método especificar Overrides e não houver nenhum método base correspondente para substituir, ocorrerá um erro em tempo de compilação. Um método de substituição pode não especificar Shadows.

  • Um método não pode substituir outro método se o domínio de acessibilidade do método de substituição não for igual ao domínio de acessibilidade do método que está sendo substituído. A única exceção é que um método substituindo um Protected Friend método em outro assembly que não tem Friend acesso deve especificar Protected (não Protected Friend).

  • Private os métodos não podem ser Overridable, NotOverridableou MustOverride, nem podem sobrepor-se a outros métodos.

  • Os métodos das NotInheritable classes não podem ser declarados Overridable ou MustOverride.

O exemplo a seguir ilustra as diferenças entre métodos substituíveis e não substituíveis:

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

No exemplo, a classe Base introduz um método F e um Overridable método G. A classe Derived introduz um novo método F, sombreando assim o método herdado F, e também substitui o método Gherdado. O exemplo produz a seguinte saída:

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

Observe que a instrução b.G() invoca Derived.G, não Base.G. Isso ocorre porque o tipo de tempo de execução da instância (que é Derived) em vez do tipo de tempo de compilação da instância (que é Base) determina a implementação real do método a ser invocada.

Métodos Partilhados

O Shared modificador indica que um método é um método compartilhado. Um método compartilhado não opera em uma instância específica de um tipo e pode ser invocado diretamente de um tipo em vez de através de uma instância específica de um tipo. É válido, no entanto, usar uma instância para qualificar um método compartilhado. É inválido referir-se a Me, MyClassou MyBase em um método compartilhado. Os métodos partilhados não podem ser Overridable, NotOverridableou MustOverride, e não podem substituir os métodos. Os métodos definidos em módulos e interfaces padrão podem não especificar Shared, porque eles já estão implicitamente Shared já.

Um método declarado em uma estrutura ou classe sem um Shared modificador é um método de instância. Um método de instância opera em uma determinada instância de um tipo. Os métodos de instância só podem ser invocados por meio de uma instância de um tipo e podem se referir à instância por meio da Me expressão.

O exemplo a seguir ilustra as regras para acessar membros compartilhados e de instância:

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

O método F mostra que, em um membro da função de instância, um identificador pode ser usado para acessar membros da instância e membros compartilhados. Método G mostra que, em um membro de função compartilhada, é um erro acessar um membro da instância por meio de um identificador. O método Main mostra que, em uma expressão de acesso de membro, os membros da instância devem ser acessados por meio de instâncias, mas os membros compartilhados podem ser acessados por meio de tipos ou instâncias.

Parâmetros do método

Um parâmetro é uma variável que pode ser usada para passar informações para dentro e para fora de um método. Os parâmetros de um método são declarados pela lista de parâmetros do método, que consiste em um ou mais parâmetros separados por vírgulas.

ParameterList
    : Parameter ( Comma Parameter )*
    ;

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

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

ParameterIdentifier
    : Identifier IdentifierModifiers
    ;

Se nenhum tipo for especificado para um parâmetro e semântica estrita for usada, ocorrerá um erro em tempo de compilação. Caso contrário, o tipo padrão é Object ou o tipo do caractere de tipo do parâmetro. Mesmo sob semântica permissiva, se um parâmetro inclui uma As cláusula, todos os parâmetros devem especificar tipos.

Os parâmetros são especificados ByValcomo parâmetros de valor, referência, opcional ou paramarray pelos modificadores , , ByRefOptional, e ParamArray, respectivamente. Um parâmetro que não especifica ByRef ou ByVal assume como ByValpadrão .

Os nomes de parâmetros têm escopo para todo o corpo do método e são sempre acessíveis publicamente. Uma invocação de método cria uma cópia, específica para essa invocação, dos parâmetros, e a lista de argumentos da invocação atribui valores ou referências de variáveis aos parâmetros recém-criados. Como as declarações de método externo e as declarações delegadas não têm corpo, nomes de parâmetros duplicados são permitidos em listas de parâmetros, mas desencorajados.

O identificador pode ser seguido pelo modificador ? de nome anulável para indicar que é anulável e também por modificadores de nome de matriz para indicar que é uma matriz. Podem ser combinados, por exemplo, "ByVal x?() As Integer". Não é permitido usar limites de matriz explícitos; Além disso, se o modificador de nome anulável estiver presente, uma As cláusula deve estar presente.

Parâmetros de valor

Um parâmetro value é declarado com um modificador explícito ByVal . Se o ByVal modificador for usado, o ByRef modificador não pode ser especificado. Um parâmetro value surge com a invocação do membro ao qual o parâmetro pertence e é inicializado com o valor do argumento dado na invocação. Um parâmetro value deixa de existir após o retorno do membro.

Permite-se a um método atribuir novos valores a um parâmetro de valor. Tais atribuições afetam apenas o local de armazenamento local representado pelo parâmetro value; eles não têm efeito sobre o argumento real dado na invocação do método.

Um parâmetro value é usado quando o valor de um argumento é passado para um método e as modificações do parâmetro não afetam o argumento original. Um parâmetro de valor refere-se à sua própria variável, que é distinta da variável do argumento correspondente. Esta variável é inicializada copiando o valor do argumento correspondente. O exemplo a seguir mostra um método F que tem um parâmetro value chamado p:

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

O exemplo produz a seguinte saída, mesmo que o parâmetro p value seja modificado:

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

Parâmetros de referência

Um parâmetro de referência é um parâmetro declarado com um ByRef modificador. Se o ByRef modificador for especificado, o ByVal modificador não pode ser usado. Um parâmetro de referência não cria um novo local de armazenamento. Em vez disso, um parâmetro de referência representa a variável dada como o argumento no método ou invocação do construtor. Conceptualmente, o valor de um parâmetro de referência é sempre o mesmo que a variável subjacente.

Os parâmetros de referência atuam em dois modos, como aliases ou através de copy-in copy-back.

Pseudónimos. Um parâmetro de referência é usado quando o parâmetro atua como um alias para um argumento fornecido pelo chamador. Um parâmetro de referência não define uma variável em si, mas refere-se à variável do argumento correspondente. As modificações de um parâmetro de referência afetam direta e imediatamente o argumento correspondente. O exemplo a seguir mostra um método Swap que tem dois parâmetros de referência:

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

A saída do programa é:

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

Para a invocação do método Swap na classe Main, a representa x, e b representa y. Assim, a invocação tem o efeito de trocar os valores de x e y.

Em um método que usa parâmetros de referência, é possível que vários nomes representem o mesmo local de armazenamento:

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

No exemplo, a invocação do método F em G passa uma referência para s ambos e ab. Assim, para essa invocação, os nomes s, ae b todos se referem ao mesmo local de armazenamento, e as três atribuições modificam a variável sde instância .

Copy-in copy-back. Se o tipo da variável que está sendo passada para um parâmetro de referência não for compatível com o tipo do parâmetro de referência, ou se uma não-variável (por exemplo, uma propriedade) for passada como um argumento para um parâmetro de referência, ou se a invocação for tardia, então uma variável temporária será alocada e passada para o parâmetro de referência. O valor que está sendo passado será copiado para essa variável temporária antes que o método seja invocado e será copiado de volta para a variável original (se houver uma e se for gravável) quando o método retornar. Assim, um parâmetro de referência pode não conter necessariamente uma referência ao armazenamento exato da variável que está sendo passada, e quaisquer alterações no parâmetro de referência podem não ser refletidas na variável até que o método saia. Por exemplo:

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

No caso da primeira invocação de F, uma variável temporária é criada e o valor da propriedade G é atribuído a ela e passado para F. Após o retorno de F, o valor na variável temporária é atribuído de volta à propriedade de G. No segundo caso, outra variável temporária é criada e o valor de d é atribuído a ela e passado para F. Ao retornar de F, o valor na variável temporária é convertido de volta para o tipo da variável Derivede atribuído a d. Como o valor que está sendo passado de volta não pode ser convertido para Derived, uma exceção é lançada em tempo de execução.

Parâmetros opcionais

Um parâmetro opcional é declarado com o Optional modificador. Os parâmetros que seguem um parâmetro opcional na lista de parâmetros formal também devem ser opcionais; A falha ao especificar o Optional modificador nos seguintes parâmetros desencadeará um erro em tempo de compilação. Um parâmetro opcional de algum tipo tipo anulável T? ou tipo T não anulável deve especificar uma expressão e constante a ser usada como um valor padrão se nenhum argumento for especificado. Se e for avaliado como Nothing do tipo Object, o valor padrão do tipo de parâmetro será usado como padrão para o parâmetro. Caso contrário, CType(e, T) deve ser uma expressão constante e é tomado como o padrão para o parâmetro.

Parâmetros opcionais são a única situação em que um inicializador em um parâmetro é válido. A inicialização é sempre feita como parte da expressão de invocação, não dentro do corpo do método em si.

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

A saída do programa é:

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

Os parâmetros opcionais não podem ser especificados em declarações de delegado ou de evento, nem em expressões lambda.

Parâmetros ParamArray

ParamArray parâmetros são declarados com o ParamArray modificador. Se o ParamArray modificador estiver presente, o ByVal modificador deve ser especificado, e nenhum outro parâmetro pode usar o ParamArray modificador. O ParamArray tipo do parâmetro deve ser uma matriz unidimensional e deve ser o último parâmetro na lista de parâmetros.

Um ParamArray parâmetro representa um número indeterminado de parâmetros do tipo ParamArraydo . Dentro do próprio método, um ParamArray parâmetro é tratado como seu tipo declarado e não tem semântica especial. Um ParamArray parâmetro é implicitamente opcional, com um valor padrão de uma matriz unidimensional vazia do tipo ParamArraydo .

A ParamArray permite que os argumentos sejam especificados de uma de duas maneiras em uma invocação de método:

  • O argumento dado para um ParamArray pode ser uma única expressão de um tipo que se amplia para o ParamArray tipo. Neste caso, o ParamArray atua precisamente como um parâmetro de valor.

  • Como alternativa, a invocação pode especificar zero ou mais argumentos para o ParamArray, onde cada argumento é uma expressão de um tipo que é implicitamente conversível para o tipo de elemento do ParamArray. Nesse caso, a invocação cria uma instância do ParamArray tipo com um comprimento correspondente ao número de argumentos, inicializa os elementos da instância da matriz com os valores de argumento fornecidos e usa a instância de matriz recém-criada como o argumento real.

Exceto por permitir um número variável de argumentos em uma invocação, a ParamArray é precisamente equivalente a um parâmetro de valor do mesmo tipo, como ilustra o exemplo a seguir.

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

O exemplo produz a saída

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

A primeira invocação de F simplesmente passa a matriz a como um parâmetro de valor. A segunda invocação de cria automaticamente uma matriz de quatro elementos com os valores de elemento fornecidos e passa essa instância de matriz como um parâmetro de F valor. Da mesma forma, a terceira invocação de cria uma matriz de elemento zero e passa essa instância como um parâmetro de F valor. A segunda e terceira invocações equivalem precisamente à escrita:

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

ParamArray Os parâmetros não podem ser especificados em declarações de delegado ou de evento.

Tratamento de eventos

Os métodos podem manipular declarativamente eventos gerados por objetos em instância ou variáveis compartilhadas. Para manipular eventos, uma declaração de método especifica a Handles palavra-chave e lista um ou mais eventos.

HandlesClause
    : ( 'Handles' EventHandlesList )?
    ;

EventHandlesList
    : EventMemberSpecifier ( Comma EventMemberSpecifier )*
    ;

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

Um evento na Handles lista é especificado por dois identificadores separados por um ponto:

  • O primeiro identificador deve ser uma instância ou variável compartilhada no tipo que contém que especifica o WithEvents modificador ou a palavra-chave ou MeMyClass ouMyBase, caso contrário, ocorre um erro em tempo de compilação. Essa variável contém o objeto que gerará os eventos manipulados por esse método.

  • O segundo identificador deve especificar um membro do tipo do primeiro identificador. O membro deve ser um evento, e pode ser compartilhado. Se uma variável compartilhada for especificada para o primeiro identificador, o evento deverá ser compartilhado ou um erro resultará.

Um método manipulador é considerado um manipulador M de eventos válido para um evento E se a instrução AddHandler E, AddressOf M também for válida. Ao contrário de uma AddHandler instrução, no entanto, manipuladores de eventos explícitos permitem manipular um evento com um método sem argumentos, independentemente de semântica estrita estar sendo usada ou não:

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

Um único membro pode manipular vários eventos correspondentes e vários métodos podem manipular um único evento. A acessibilidade de um método não tem efeito sobre sua capacidade de manipular eventos. O exemplo a seguir mostra como um método pode manipular eventos:

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

Isto irá imprimir:

Raised
Raised

Um tipo herda todos os manipuladores de eventos fornecidos por seu tipo base. Um tipo derivado não pode de forma alguma alterar os mapeamentos de eventos que herda de seus tipos base, mas pode adicionar manipuladores adicionais ao evento.

Métodos da Extensão

Os métodos podem ser adicionados a tipos de fora da declaração de tipo usando métodos de extensão. Métodos de extensão são métodos com o System.Runtime.CompilerServices.ExtensionAttribute atributo aplicado a eles. Eles só podem ser declarados em módulos padrão e devem ter pelo menos um parâmetro, que especifica o tipo que o método estende. Por exemplo, o seguinte método de extensão estende o tipo String:

Imports System.Runtime.CompilerServices

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

Nota. Embora o Visual Basic exija que os métodos de extensão sejam declarados em um módulo padrão, outras linguagens como C# podem permitir que eles sejam declarados em outros tipos de tipos. Contanto que os métodos sigam as outras convenções descritas aqui e o tipo que contém não é um tipo genérico aberto e não pode ser instanciado, o Visual Basic reconhecerá os métodos de extensão.

Quando um método de extensão é invocado, a instância em que ele está sendo invocado é passada para o primeiro parâmetro. O primeiro parâmetro não pode ser declarado Optional ou ParamArray. Qualquer tipo, incluindo um parâmetro type, pode aparecer como o primeiro parâmetro de um método de extensão. Por exemplo, os seguintes métodos estendem os tipos Integer(), qualquer tipo que implementa System.Collections.Generic.IEnumerable(Of T)e qualquer tipo:

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

Como mostra o exemplo anterior, as interfaces podem ser estendidas. Os métodos de extensão de interface fornecem a implementação do método, de modo que os tipos que implementam uma interface que tem métodos de extensão definidos nela ainda implementam apenas os membros originalmente declarados pela interface. Por exemplo:

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

Os métodos de extensão também podem ter restrições de tipo em seus parâmetros de tipo e, assim como com métodos genéricos sem extensão, o argumento de tipo pode ser inferido:

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

Os métodos de extensão também podem ser acessados por meio de expressões de instância implícitas dentro do tipo que está sendo estendido:

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

Para fins de acessibilidade, os métodos de extensão também são tratados como membros do módulo padrão em que são declarados - eles não têm acesso extra aos membros do tipo que estão estendendo além do acesso que têm em virtude de seu contexto de declaração.

Os métodos de extensões só estão disponíveis quando o método de módulo padrão está no escopo. Caso contrário, o tipo original não parecerá ter sido estendido. Por exemplo:

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

Referir-se a um tipo quando apenas um método de extensão no tipo está disponível ainda produzirá um erro em tempo de compilação.

É importante observar que os métodos de extensão são considerados membros do tipo em todos os contextos em que os membros estão vinculados, como o padrão fortemente tipado For Each . Por exemplo:

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

Também podem ser criados delegados que se referem a métodos de extensão. Assim, o código:

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

é aproximadamente equivalente a:

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

Nota. Visual Basic normalmente insere uma verificação em uma chamada de método de instância que faz com que a ocorra se System.NullReferenceException a instância em que o método está sendo invocado é Nothing. No caso de métodos de extensão, não há uma maneira eficiente de inserir essa verificação, portanto, os métodos de extensão precisarão verificar explicitamente se há Nothing.

Nota. Um tipo de valor será encaixotado ao ser passado como um ByVal argumento para um parâmetro digitado como uma interface. Isto implica que os efeitos secundários do método de extensão funcionarão numa cópia da estrutura em vez do original. Embora a linguagem não coloque restrições no primeiro argumento de um método de extensão, recomenda-se que os métodos de extensão não sejam usados para estender tipos de valor ou que, ao estender tipos de valor, o primeiro parâmetro seja passado ByRef para garantir que os efeitos colaterais operem no valor original.

Métodos parciais

Um método parcial é um método que especifica uma assinatura, mas não o corpo do método. O corpo do método pode ser fornecido por outra declaração de método com o mesmo nome e assinatura, provavelmente em outra declaração parcial do tipo. Por exemplo:

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

Neste exemplo, uma declaração parcial da classe MyForm declara um método ValidateControls parcial sem implementação. O construtor na declaração parcial chama o método parcial, mesmo que não haja nenhum corpo fornecido no arquivo. A outra declaração parcial fornece MyForm então a aplicação do método.

Métodos parciais podem ser chamados independentemente de um corpo ter sido fornecido; Se nenhum corpo de método for fornecido, a chamada será ignorada. Por exemplo:

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

Todas as expressões que são passadas como argumentos para uma chamada de método parcial que é ignorada também são ignoradas e não avaliadas. (Observação. Isso significa que os métodos parciais são uma maneira muito eficiente de fornecer comportamento que é definido em dois tipos parciais, uma vez que os métodos parciais não têm custo se não forem usados.)

A declaração parcial do método deve ser declarada como Private e deve ser sempre uma sub-rotina sem declarações no seu corpo. Os métodos parciais não podem, por si só, implementar métodos de interface, embora o método que fornece o seu corpo possa.

Apenas um método pode fornecer um corpo a um método parcial. Um método que fornece um corpo a um método parcial deve ter a mesma assinatura que o método parcial, as mesmas restrições em quaisquer parâmetros de tipo, os mesmos modificadores de declaração e os mesmos nomes de parâmetros e parâmetros de tipo. Os atributos no método parcial e o método que fornece seu corpo são mesclados, assim como quaisquer atributos nos parâmetros dos métodos. Da mesma forma, a lista de eventos que os métodos manipulam é mesclada. Por exemplo:

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

Construtores

Os construtores são métodos especiais que permitem o controle sobre a inicialização. Eles são executados depois que o programa começa ou quando uma instância de um tipo é criada. Ao contrário de outros membros, os construtores não são herdados e não introduzem um nome no espaço de declaração de um tipo. Os construtores só podem ser invocados por expressões de criação de objeto ou pelo .NET Framework; nunca podem ser invocados diretamente.

Nota. Os construtores têm a mesma restrição de posicionamento de linha que as sub-rotinas. A instrução inicial, a instrução final e o bloco devem aparecer no início de uma linha lógica.

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

ConstructorModifier
    : AccessModifier
    | 'Shared'
    ;

Construtores de instância

Os construtores de instância inicializam instâncias de um tipo e são executados pelo .NET Framework quando uma instância é criada. A lista de parâmetros de um construtor está sujeita às mesmas regras que a lista de parâmetros de um método. Os construtores de instância podem estar sobrecarregados.

Todos os construtores em tipos de referência devem invocar outro construtor. Se a invocação for explícita, deve ser a primeira instrução no corpo do método do construtor. A instrução pode invocar outro dos construtores de instância do tipo -- por exemplo, ou MyClass.New(...) -- ou, se não for uma estrutura, Me.New(...) pode invocar um construtor de instância do tipo base do tipo -- por exemplo, MyBase.New(...). É inválido para um construtor invocar a si mesmo. Se um construtor omite uma chamada para outro construtor, MyBase.New() está implícito. Se não houver nenhum construtor de tipo base sem parâmetros, ocorrerá um erro em tempo de compilação. Como Me não é considerado construído até depois da chamada para um construtor de classe base, os parâmetros para uma instrução de invocação do construtor não podem fazer referência Mea , MyClassou MyBase implícita ou explicitamente.

Quando a primeira instrução de um construtor é da forma MyBase.New(...), o construtor executa implicitamente as inicializações especificadas pelos inicializadores de variáveis de instância das variáveis de instância declaradas no tipo. Isso corresponde a uma sequência de atribuições que são executadas imediatamente após invocar o construtor de tipo base direto. Essa ordenação garante que todas as variáveis de instância base sejam inicializadas por seus inicializadores de variáveis antes que quaisquer instruções que tenham acesso à instância sejam executadas. Por exemplo:

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

Quando New B() é usado para criar uma instância do , a seguinte saída é produzida B:

x = 1, y = 1

O valor de é 1 porque o inicializador de variável é executado depois que o construtor de y classe base é invocado. Os inicializadores de variáveis são executados na ordem textual em que aparecem na declaração de tipo.

Quando um tipo declara apenas Private construtores, não é possível, em geral, que outros tipos derivem do tipo ou criem instâncias do tipo, a única exceção são os tipos aninhados dentro do tipo. Private Os construtores são comumente usados em tipos que contêm apenas Shared membros.

Se um tipo não contém declarações de construtor de instância, um construtor padrão é fornecido automaticamente. O construtor padrão simplesmente invoca o construtor sem parâmetros do tipo base direto. Se o tipo base direto não tiver um construtor sem parâmetros acessível, ocorrerá um erro em tempo de compilação. O tipo de acesso declarado para o construtor padrão é Public a menos que o tipo seja MustInherit, caso em que o construtor padrão é Protected.

Nota. O acesso padrão para o construtor padrão de um MustInherit tipo é Protected porque MustInherit as classes não podem ser criadas diretamente. Portanto, não faz sentido fazer o construtor Publicpadrão.

No exemplo a seguir, um construtor padrão é fornecido porque a classe não contém declarações de construtor:

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

Assim, o exemplo é precisamente equivalente ao seguinte:

Class Message
    Dim sender As Object
    Dim text As String

    Sub New()
    End Sub
End Class

Os construtores padrão que são emitidos em uma classe gerada pelo designer marcada com o atributo Microsoft.VisualBasic.CompilerServices.DesignerGeneratedAttribute chamarão o método Sub InitializeComponent(), se ele existir, após a chamada para o construtor base. (Observação. Isso permite que os arquivos gerados pelo designer, como aqueles criados pelo designer WinForms, omitam o construtor no arquivo do designer. Isto permite ao programador especificá-lo por si próprio, se assim o desejar.)

Construtores compartilhados

Os construtores compartilhados inicializam as variáveis compartilhadas de um tipo; Eles são executados depois que o programa começa a ser executado, mas antes de qualquer referência a um membro do tipo. Um construtor compartilhado especifica o Shared modificador, a menos que esteja em um módulo padrão, caso em que o Shared modificador está implícito.

Ao contrário dos construtores de instância, os construtores compartilhados têm acesso público implícito, não têm parâmetros e não podem chamar outros construtores. Antes da primeira instrução em um construtor compartilhado, o construtor compartilhado executa implicitamente as inicializações especificadas pelos inicializadores de variáveis das variáveis compartilhadas declaradas no tipo. Isso corresponde a uma sequência de atribuições que são executadas imediatamente após a entrada no construtor. Os inicializadores de variáveis são executados na ordem textual em que aparecem na declaração de tipo.

O exemplo a seguir mostra uma Employee classe com um construtor compartilhado que inicializa uma variável compartilhada:

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

Existe um construtor compartilhado separado para cada tipo genérico fechado. Como o construtor compartilhado é executado exatamente uma vez para cada tipo fechado, é um lugar conveniente para impor verificações em tempo de execução no parâmetro type que não pode ser verificado em tempo de compilação por meio de restrições. Por exemplo, o tipo a seguir usa um construtor compartilhado para impor que o parâmetro type é Integer ou Double:

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

Exatamente quando os construtores compartilhados são executados é principalmente dependente da implementação, embora várias garantias sejam fornecidas se um construtor compartilhado for explicitamente definido:

  • Os construtores compartilhados são executados antes do primeiro acesso a qualquer campo estático do tipo.

  • Os construtores compartilhados são executados antes da primeira invocação de qualquer método estático do tipo.

  • Os construtores compartilhados são executados antes da primeira invocação de qualquer construtor para o tipo.

As garantias acima não se aplicam na situação em que um construtor compartilhado é implicitamente criado para inicializadores compartilhados. A saída do exemplo a seguir é incerta, porque a ordem exata do carregamento e, portanto, da execução do construtor compartilhado não está definida:

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

A saída pode ser uma das seguintes:

Init A
A.F
Init B
B.F

ou

Init B
Init A
A.F
B.F

Por outro lado, o exemplo a seguir produz resultados previsíveis. Observe que o construtor para a classe A nunca executa, mesmo que a Shared classe B derive dele:

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

A saída é:

Init B
B.G

Também é possível construir dependências circulares que permitem Shared que variáveis com inicializadores de variáveis sejam observadas em seu estado de valor padrão, como no exemplo a seguir:

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

Isto produz a saída:

X = 1, Y = 2

Para executar o Main método, o sistema primeiro carrega a classe B. O Shared construtor de class B prossegue para calcular o valor inicial de , que recursivamente faz com que a classe A seja carregada Yporque o valor de A.X é referenciado. O Shared construtor de classe A , por sua vez, prossegue para calcular o valor inicial de X, e, ao fazer isso, busca o valor padrão de Y, que é zero. A.X é, portanto, inicializado em 1. O processo de carregamento A então é concluído, retornando ao cálculo do valor inicial de Y, cujo resultado se torna 2.

Se o Main método tivesse sido localizado na classe A, o exemplo teria produzido a seguinte saída:

X = 2, Y = 1

Evite referências circulares em Shared inicializadores variáveis, pois geralmente é impossível determinar a ordem em que as classes que contêm tais referências são carregadas.

Eventos

Os eventos são usados para notificar o código de uma ocorrência específica. Uma declaração de evento consiste em um identificador, um tipo de delegado ou uma lista de parâmetros, e uma cláusula opcional 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'
    ;

Se um tipo de delegado for especificado, o tipo de delegado pode não ter um tipo de retorno. Se uma lista de parâmetros for especificada, ela pode não conter Optional parâmetros ou ParamArray . O domínio de acessibilidade dos tipos de parâmetros e/ou tipo de delegado deve ser o mesmo ou um superconjunto do domínio de acessibilidade do próprio evento. Os eventos podem ser compartilhados especificando o Shared modificador.

Além do nome do membro adicionado ao espaço de declaração do tipo, uma declaração de evento declara implicitamente vários outros membros. Dado um evento chamado X, os seguintes membros são adicionados ao espaço de declaração:

  • Se a forma da declaração for uma declaração de método, uma classe delegada aninhada nomeada XEventHandler será introduzida. A classe de delegado aninhado corresponde à declaração de método e tem a mesma acessibilidade que o evento. Os atributos na lista de parâmetros aplicam-se aos parâmetros da classe delegada.

  • Uma Private variável de instância digitada como delegado, chamada XEvent.

  • Dois métodos nomeados add_X e remove_X que não podem ser invocados, substituídos ou sobrecarregados.

Se um tipo tentar declarar um nome que corresponda a um dos nomes acima, um erro em tempo de compilação resultará e as declarações implícitas add_X e remove_X serão ignoradas para fins de vinculação de nome. Não é possível substituir ou sobrecarregar nenhum dos membros introduzidos, embora seja possível sombreá-los em tipos derivados. Por exemplo, a declaração de classe

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

é equivalente à seguinte declaração

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

Declarar um evento sem especificar um tipo de delegado é a sintaxe mais simples e compacta, mas tem a desvantagem de declarar um novo tipo de delegado para cada evento. Por exemplo, no exemplo a seguir, três tipos de delegados ocultos são criados, mesmo que todos os três eventos tenham a mesma lista de parâmetros:

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

No exemplo a seguir, os eventos simplesmente usam o mesmo delegado, 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

Os eventos podem ser tratados de duas maneiras: estática ou dinamicamente. A manipulação estática de eventos é mais simples e requer apenas uma WithEvents variável e uma Handles cláusula. No exemplo a seguir, a classe Form1 manipula estaticamente o evento Click do objeto Button:

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

A manipulação dinâmica de eventos é mais complexa porque o evento deve ser explicitamente conectado e desconectado no código. A instrução AddHandler adiciona um manipulador para um evento e a instrução RemoveHandler remove um manipulador para um evento. O próximo exemplo mostra uma classe Form1 que adiciona Button1_Click como um manipulador de eventos para Button1o Click evento do :

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

No método Disconnect, o manipulador de eventos é removido.

Eventos personalizados

Conforme discutido na seção anterior, as declarações de evento definem implicitamente um campo, um add_ método e um remove_ método que são usados para controlar os manipuladores de eventos. Em algumas situações, no entanto, pode ser desejável fornecer código personalizado para controlar manipuladores de eventos. Por exemplo, se uma classe define quarenta eventos dos quais apenas alguns serão manipulados, usar uma tabela de hash em vez de quarenta campos para rastrear os manipuladores para cada evento pode ser mais eficiente. Os eventos personalizados permitem que os métodos e remove_X sejam definidos explicitamente, o que permite o add_X armazenamento personalizado para manipuladores de eventos.

Os eventos personalizados são declarados da mesma forma que os eventos que especificam um tipo de delegado são declarados, com a exceção de que a palavra-chave deve preceder a Event palavra-chaveCustom. Uma declaração de evento aduaneiro contém três declarações: uma AddHandler declaração, uma RemoveHandler declaração e uma RaiseEvent declaração. Nenhuma das declarações pode ter modificadores, embora possam ter atributos.

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
    ;

Por exemplo:

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

A AddHandler declaração e RemoveHandler usa um ByVal parâmetro, que deve ser do tipo delegado do evento. Quando uma AddHandler instrução ou RemoveHandler é executada (ou uma Handles cláusula lida automaticamente com um evento), a declaração correspondente será chamada. A RaiseEvent declaração usa os mesmos parâmetros que o delegado de evento e será chamada quando uma RaiseEvent instrução for executada. Todas as declarações devem ser fornecidas e são consideradas subrotinas.

Observe que AddHandler, RemoveHandler e RaiseEvent as declarações têm a mesma restrição de posicionamento de linha que as sub-rotinas. A instrução inicial, a instrução final e o bloco devem aparecer no início de uma linha lógica.

Além do nome do membro adicionado ao espaço de declaração do tipo, uma declaração de evento personalizada declara implicitamente vários outros membros. Dado um evento chamado X, os seguintes membros são adicionados ao espaço de declaração:

  • Um método chamado add_X, correspondente à AddHandler declaração.

  • Um método chamado remove_X, correspondente à RemoveHandler declaração.

  • Um método chamado fire_X, correspondente à RaiseEvent declaração.

Se um tipo tentar declarar um nome que corresponda a um dos nomes acima, um erro em tempo de compilação resultará e as declarações implícitas serão todas ignoradas para fins de vinculação de nome. Não é possível substituir ou sobrecarregar nenhum dos membros introduzidos, embora seja possível sombreá-los em tipos derivados.

Nota. Custom não é uma palavra reservada.

Eventos personalizados em assemblies do WinRT

A partir do Microsoft Visual Basic 11.0, os eventos declarados em um arquivo compilado com /target:winmdobj, ou declarados em uma interface em tal arquivo e, em seguida, implementados em outro lugar, são tratados de forma um pouco diferente.

  • As ferramentas externas usadas para construir o winmd normalmente permitirão apenas certos tipos de delegados, como System.EventHandler(Of T) ou System.TypedEventHandle(Of T, U), e não permitirão outros.

  • O XEvent campo tem o tipo System.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenTable(Of T) onde T é o tipo delegado.

  • O acessador AddHandler retorna um System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken, e o acessador RemoveHandler usa um único parâmetro do mesmo tipo.

Aqui está um exemplo de tal evento personalizado.

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

Constantes

Uma constante é um valor constante que é um membro de um tipo.

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

ConstantModifier
    : AccessModifier
    | 'Shadows'
    ;

ConstantDeclarators
    : ConstantDeclarator ( Comma ConstantDeclarator )*
    ;

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

As constantes são implicitamente compartilhadas. Se a declaração contiver uma As cláusula, a cláusula especifica o tipo de membro introduzido pela declaração. Se o tipo é omitido, então o tipo da constante é inferido. O tipo de constante pode ser apenas um tipo primitivo ou Object. Se uma constante é digitada como Object e não há nenhum caractere de tipo, o tipo real da constante será o tipo da expressão constante. Caso contrário, o tipo da constante é o tipo do caractere de tipo da constante.

O exemplo a seguir mostra uma classe chamada Constants que tem duas constantes públicas:

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

As constantes podem ser acessadas através da classe, como no exemplo a seguir, que imprime os valores de Constants.A e Constants.B.

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

Uma declaração constante que declara várias constantes é equivalente a várias declarações de constantes únicas. O exemplo a seguir declara três constantes em uma instrução de declaração.

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

Esta declaração é equivalente ao seguinte:

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

O domínio de acessibilidade do tipo da constante deve ser o mesmo ou um superconjunto do domínio de acessibilidade da própria constante. A expressão constante deve produzir um valor do tipo da constante ou de um tipo que seja implicitamente conversível para o tipo da constante. A expressão constante pode não ser circular; ou seja, uma constante pode não ser definida em termos de si mesma.

O compilador avalia automaticamente as declarações constantes na ordem apropriada. No exemplo a seguir, o compilador primeiro avalia Y, depois Ze, finalmente X, produzindo os valores 10, 11 e 12, respectivamente.

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

Quando um nome simbólico para um valor constante é desejado, mas o tipo do valor não é permitido em uma declaração constante ou quando o valor não pode ser calculado em tempo de compilação por uma expressão constante, uma variável somente leitura pode ser usada em vez disso.

Instância e variáveis compartilhadas

Uma instância ou variável compartilhada é um membro de um tipo que pode armazenar informações.

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
    ;

O Dim modificador deve ser especificado se nenhum modificador for especificado, mas pode ser omitido de outra forma. Uma única declaração de variável pode incluir vários declaradores de variáveis; Cada declarador de variável introduz uma nova instância ou membro compartilhado.

Se um inicializador for especificado, apenas uma instância ou variável compartilhada poderá ser declarada pelo declarador de variável:

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

Esta restrição não se aplica aos inicializadores de objeto:

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

Uma variável declarada com o Shared modificador é uma variável compartilhada. Uma variável compartilhada identifica exatamente um local de armazenamento, independentemente do número de instâncias do tipo que são criadas. Uma variável compartilhada passa a existir quando um programa começa a ser executado e deixa de existir quando o programa é encerrado.

Uma variável compartilhada é compartilhada apenas entre instâncias de um determinado tipo genérico fechado. Por exemplo, o programa:

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

Imprime:

1
1
2

Uma variável declarada sem o Shared modificador é chamada de variável de instância. Cada instância de uma classe contém uma cópia separada de todas as variáveis de instância da classe. Uma variável de instância de um tipo de referência surge quando uma nova instância desse tipo é criada e deixa de existir quando não há referências a essa instância e o Finalize método foi executado. Uma variável de instância de um tipo de valor tem exatamente o mesmo tempo de vida que a variável à qual pertence. Em outras palavras, quando uma variável de um tipo de valor passa a existir ou deixa de existir, o mesmo acontece com a variável de instância do tipo de valor.

Se o declarador contiver uma As cláusula, esta especifica o tipo de membros introduzidos pela declaração. Se o tipo for omitido e a semântica estrita estiver sendo usada, ocorrerá um erro em tempo de compilação. Caso contrário, o tipo dos membros é implicitamente Object ou o tipo do caractere de tipo dos membros.

Nota. Não há ambiguidade na sintaxe: se um declarador omitir um tipo, ele sempre usará o tipo de um declarador a seguir.

O domínio de acessibilidade de uma instância ou do tipo de elemento de matriz de uma instância ou variável compartilhada deve ser o mesmo ou um superconjunto do domínio de acessibilidade da instância ou da própria variável compartilhada.

O exemplo a seguir mostra uma Color classe que tem variáveis de instância interna chamadas redPart, greenParte bluePart:

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 Variáveis

Quando uma instância ou declaração de variável compartilhada inclui um ReadOnly modificador, as atribuições às variáveis introduzidas pela declaração podem ocorrer apenas como parte da declaração ou em um construtor na mesma classe. Especificamente, as atribuições a uma instância somente leitura ou variável compartilhada são permitidas somente nas seguintes situações:

  • Na declaração de variável que introduz a instância ou variável compartilhada (incluindo um inicializador de variável na declaração).

  • Para uma variável de instância, nos construtores de instância da classe que contém a declaração de variável. A variável de instância só pode ser acessada de forma não qualificada ou através de Me ou MyClass.

  • Para uma variável compartilhada, no construtor compartilhado da classe que contém a declaração de variável compartilhada.

Uma variável somente leitura compartilhada é útil quando um nome simbólico para um valor constante é desejado, mas quando o tipo do valor não é permitido em uma declaração constante ou quando o valor não pode ser calculado em tempo de compilação por uma expressão constante.

Segue-se um exemplo da primeira aplicação deste tipo, em que as variáveis de cor partilhadas são declaradas ReadOnly para evitar que sejam alteradas por outros programas:

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

Constantes e variáveis compartilhadas somente leitura têm semânticas diferentes. Quando uma expressão faz referência a uma constante, o valor da constante é obtido em tempo de compilação, mas quando uma expressão faz referência a uma variável compartilhada somente leitura, o valor da variável compartilhada não é obtido até o tempo de execução. Considere a seguinte aplicação, que consiste em dois programas separados.

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

Os namespaces Program1 e Program2 denotam dois programas que são compilados separadamente. Como a variável Program1.Utils.X é declarada como Shared ReadOnly, a saída do Console.WriteLine valor pela instrução não é conhecida em tempo de compilação, mas é obtida em tempo de execução. Assim, se o valor de X for alterado e Program1 for recompilado, a Console.WriteLine instrução produzirá o novo valor, mesmo que Program2 não seja recompilado. No entanto, se X tivesse sido uma constante, o valor de teria sido obtido no momento Program2 em que foi compilado, e teria permanecido X inalterado por alterações até Program1Program2 ser recompilado.

Variáveis WithEvents

Um tipo pode declarar que lida com algum conjunto de eventos gerados por uma de suas variáveis de instância ou compartilhadas declarando a instância ou variável compartilhada que gera os eventos com o WithEvents modificador. Por exemplo:

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

Neste exemplo, o método E1Handler manipula o evento E1 que é gerado pela instância do tipo Raiser armazenado na variável xde instância .

O WithEvents modificador faz com que a variável seja renomeada com um sublinhado à esquerda e substituída por uma propriedade do mesmo nome que faz a conexão do evento. Por exemplo, se o nome da variável for F, ela será renomeada para _F e uma propriedade F será implicitamente declarada. Se houver uma colisão entre o novo nome da variável e outra declaração, um erro em tempo de compilação será relatado. Todos os atributos aplicados à variável são transferidos para a variável renomeada.

A propriedade implícita criada por uma WithEvents declaração cuida de conectar e desconectar os manipuladores de eventos relevantes. Quando um valor é atribuído à variável, a propriedade primeiro chama o remove método para o evento na instância atualmente na variável (desconectando o manipulador de eventos existente, se houver). Em seguida, a atribuição é feita e a propriedade chama o add método para o evento na nova instância na variável (conectando o novo manipulador de eventos). O código a seguir é equivalente ao código acima para o módulo Testpadrão:

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

Não é válido declarar uma instância ou variável compartilhada como WithEvents se a variável fosse digitada como uma estrutura. Além disso, WithEvents não pode ser especificado em uma estrutura e WithEventsReadOnly não pode ser combinado.

Inicializadores variáveis

As declarações de variáveis compartilhadas e de instância em classes e declarações de variáveis de instância (mas não declarações de variáveis compartilhadas) em estruturas podem incluir inicializadores de variáveis. Para Shared variáveis, inicializadores de variáveis correspondem a instruções de atribuição que são executadas após o início do programa, mas antes que a Shared variável seja referenciada pela primeira vez. Por exemplo, variáveis, inicializadores de variáveis correspondem a instruções de atribuição que são executadas quando uma instância da classe é criada. As estruturas não podem ter inicializadores de variáveis de instância porque seus construtores sem parâmetros não podem ser modificados.

Considere o seguinte exemplo:

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

O exemplo produz a seguinte saída:

x = 1.4142135623731, i = 100, s = Hello

Uma atribuição a x ocorre quando a classe é carregada e as atribuições a i e s ocorrem quando uma nova instância da classe é criada.

É útil pensar em inicializadores de variáveis como instruções de atribuição que são inseridas automaticamente no bloco do construtor do tipo. O exemplo a seguir contém vários inicializadores de variável de instância.

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

O exemplo corresponde ao código mostrado abaixo, onde cada comentário indica uma instrução inserida automaticamente.

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

Todas as variáveis são inicializadas com o valor padrão de seu tipo antes que qualquer inicializador de variável seja executado. Por exemplo:

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

Como b é inicializado automaticamente para seu valor padrão quando a classe é carregada e i é automaticamente inicializado para seu valor padrão quando uma instância da classe é criada, o código anterior produz a seguinte saída:

b = False, i = 0

Cada inicializador de variável deve produzir um valor do tipo da variável ou de um tipo que seja implicitamente conversível para o tipo da variável. Um inicializador de variável pode ser circular ou referir-se a uma variável que será inicializada depois dele, caso em que o valor da variável referenciada é seu valor padrão para os fins do inicializador. Tal inicializador é de valor duvidoso.

Existem três formas de inicializadores variáveis: inicializadores regulares, inicializadores de tamanho de matriz e inicializadores de objeto. Os dois primeiros formulários aparecem após um sinal de igual que segue o nome do tipo, os dois últimos fazem parte da própria declaração. Apenas uma forma de inicializador pode ser usada em qualquer declaração específica.

Inicializadores regulares

Um inicializador regular é uma expressão que é implicitamente conversível para o tipo da variável. Ele aparece após um sinal de igual que segue o nome do tipo e deve ser classificado como um valor. Por exemplo:

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

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

Este programa produz a saída:

x = 10, y = 20

Se uma declaração de variável tiver um inicializador regular, apenas uma única variável poderá ser declarada de cada vez. Por exemplo:

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

Inicializadores de objeto

Um inicializador de objeto é especificado usando uma expressão de criação de objeto no lugar do nome do tipo. Um inicializador de objeto é equivalente a um inicializador regular que atribui o resultado da expressão de criação de objeto à variável. Então

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

é equivalente a

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

O parêntese em um inicializador de objeto é sempre interpretado como a lista de argumentos para o construtor e nunca como modificadores de tipo de matriz. Um nome de variável com um inicializador de objeto não pode ter um modificador de tipo de matriz ou um modificador de tipo anulável.

Array-Size inicializadores

Um inicializador de tamanho de matriz é um modificador no nome da variável que fornece um conjunto de limites superiores de dimensão indicados por expressões.

ArraySizeInitializationModifier
    : OpenParenthesis BoundList CloseParenthesis ArrayTypeModifiers?
    ;

BoundList
    : Bound ( Comma Bound )*
    ;

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

As expressões de limite superior devem ser classificadas como valores e devem ser implicitamente convertíveis em Integer. O conjunto de limites superiores é equivalente a um inicializador variável de uma expressão de criação de matriz com os limites superiores fornecidos. O número de dimensões do tipo de matriz é inferido a partir do inicializador de tamanho de matriz. Então

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

é equivalente a

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

Todos os limites superiores devem ser iguais ou maiores que -1, e todas as dimensões devem ter um limite superior especificado. Se o tipo de elemento da matriz que está sendo inicializada for um tipo de matriz, os modificadores de tipo de matriz irão para a direita do inicializador de tamanho de matriz. Por exemplo

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

Declara uma variável x local cujo tipo é uma matriz bidimensional de matrizes tridimensionais de Integer, inicializada para uma matriz com limites de 0..5 na primeira dimensão e 0..10 na segunda dimensão. Não é possível usar um inicializador de tamanho de matriz para inicializar os elementos de uma variável cujo tipo é uma matriz de matrizes.

Uma declaração de variável com um inicializador de tamanho de matriz não pode ter um modificador de tipo de matriz em seu tipo ou um inicializador regular.

System.MarshalByRefObject Classes

As classes que derivam da classe System.MarshalByRefObject são empacotadas através dos limites de contexto usando proxies (ou seja, por referência) em vez de cópia (ou seja, por valor). Isso significa que uma instância de tal classe pode não ser uma instância verdadeira, mas pode ser apenas um stub que controla acessos variáveis e chamadas de método através de um limite de contexto.

Como resultado, não é possível criar uma referência ao local de armazenamento de variáveis definidas em tais classes. Isso significa que as variáveis digitadas como classes derivadas não podem ser passadas para parâmetros de referência, e métodos e variáveis de variáveis digitadas como tipos de System.MarshalByRefObject valor não podem ser acessados. Em vez disso, o Visual Basic trata variáveis definidas em tais classes como se fossem propriedades (já que as restrições são as mesmas em propriedades).

Há uma exceção a esta regra: um membro implícita ou explicitamente qualificado com Me está isento das restrições acima, porque Me é sempre garantido ser um objeto real, não um proxy.

Propriedades

As propriedades são uma extensão natural das variáveis; ambos são membros nomeados com tipos associados, e a sintaxe para acessar variáveis e propriedades é a mesma. Ao contrário das variáveis, no entanto, as propriedades não denotam locais de armazenamento. Em vez disso, as propriedades têm acessadores, que especificam as instruções a serem executadas para ler ou gravar seus valores.

As propriedades são definidas com declarações de propriedade. A primeira parte de uma declaração de propriedade é semelhante a uma declaração de campo. A segunda parte inclui um Get acessor e/ou um Set acessador.

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
    ;

No exemplo abaixo, a Button classe define uma Caption propriedade.

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

Com base na Button classe acima, o seguinte é um exemplo de uso da Caption propriedade:

Dim okButton As Button = New Button()

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

Aqui, o Set acessador é invocado atribuindo um valor à propriedade e o Get acessador é invocado fazendo referência à propriedade em uma expressão.

Se nenhum tipo for especificado para uma propriedade e semântica estrita estiver sendo usada, ocorrerá um erro em tempo de compilação; caso contrário, o tipo da propriedade é implicitamente Object ou o tipo do caractere de tipo da propriedade. Uma declaração de propriedade pode conter um Get acessador, que recupera o valor da propriedade, um Set acessador, que armazena o valor da propriedade, ou ambos. Como uma propriedade declara implicitamente métodos, uma propriedade pode ser declarada com os mesmos modificadores que um método. Se a propriedade é definida em uma interface ou definida com o MustOverride modificador, o corpo da propriedade e a End construção devem ser omitidos, caso contrário, ocorre um erro em tempo de compilação.

A lista de parâmetros de índice compõe a assinatura da propriedade, portanto, as propriedades podem ser sobrecarregadas nos parâmetros de índice, mas não no tipo da propriedade. A lista de parâmetros de índice é a mesma que para um método regular. No entanto, nenhum dos parâmetros pode ser modificado com o ByRef modificador e nenhum deles pode ser nomeado Value (que é reservado para o parâmetro de valor implícito no Set acessador).

Um imóvel pode ser declarado da seguinte forma:

  • Se a propriedade não especificar nenhum modificador de tipo de propriedade, a propriedade deverá ter um Get acessador e um Set acessador. Diz-se que a propriedade é uma propriedade de leitura-gravação.

  • Se a propriedade especificar o ReadOnly modificador, a propriedade deve ter um Get acessador e pode não ter um Set acessador. Diz-se que a propriedade é somente leitura. É um erro durante a compilação que uma propriedade só de leitura seja o destino de uma atribuição.

  • Se a propriedade especificar o WriteOnly modificador, a propriedade deve ter um Set acessador e pode não ter um Get acessador. Diz-se que a propriedade é propriedade somente escrita. É um erro em tempo de compilação fazer referência a uma propriedade somente gravação em uma expressão, exceto como o destino de uma atribuição ou como um argumento para um método.

Os Get e Set os acessadores de um imóvel não são membros distintos, e não é possível declarar os acessadores de um imóvel separadamente. O exemplo a seguir não declara uma única propriedade de leitura-gravação. Em vez disso, ele declara duas propriedades com o mesmo nome, uma somente leitura e uma somente gravação:

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

Como dois membros declarados na mesma classe não podem ter o mesmo nome, o exemplo causa um erro em tempo de compilação.

Por padrão, a acessibilidade de um imóvel Get e Set acessadores é a mesma que a acessibilidade do próprio imóvel. No entanto, os acessadores também Set podem especificar a Get acessibilidade separadamente da propriedade. Nesse caso, a acessibilidade de um acessador deve ser mais restritiva do que a acessibilidade da propriedade e apenas um acessor pode ter um nível de acessibilidade diferente do imóvel. Os tipos de acesso são considerados mais ou menos restritivos da seguinte forma:

  • Private é mais restritiva do que Public, Protected Friend, Protected, ou Friend.

  • Friend é mais restritiva do que Protected Friend ou Public.

  • Protected é mais restritiva do que Protected Friend ou Public.

  • Protected Friend é mais restritiva do que Public.

Quando um dos acessadores de uma propriedade é acessível, mas o outro não, a propriedade é tratada como se fosse somente leitura ou somente gravação. Por exemplo:

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

Quando um tipo derivado faz sombra a uma propriedade, a propriedade derivada oculta a propriedade sombreada em relação à leitura e à escrita. No exemplo a seguir, a P propriedade in B oculta a P propriedade em A relação à leitura e à escrita:

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

O domínio de acessibilidade do tipo de retorno ou tipos de parâmetro deve ser o mesmo ou um superconjunto do domínio de acessibilidade da própria propriedade. Uma propriedade só pode ter um Set acessador e um Get acessador.

Exceto para diferenças na sintaxe de declaração e invocação, Overridable, , NotOverridableOverrides, MustOverride, e MustInherit propriedades se comportam exatamente como Overridable, NotOverridable, Overrides, MustOverride, e MustInherit métodos. Quando uma propriedade é substituída, a propriedade de substituição deve ser do mesmo tipo (leitura-gravação, somente leitura, somente gravação). Uma Overridable propriedade não pode conter um Private acessador.

No exemplo X a seguir é uma Overridable propriedade somente leitura, Y é uma propriedade de Overridable leitura-gravação e Z é uma propriedade de MustOverride leitura-gravação.

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

Porque Z é MustOverride, a classe A que contém deve ser declarada MustInherit.

Por outro lado, uma classe que deriva da classe A é mostrada abaixo:

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

Aqui, as declarações de propriedades XeYZ substituem as propriedades base. Cada declaração de propriedade corresponde exatamente aos modificadores de acessibilidade, tipo e nome da propriedade herdada correspondente. O Get acessador de propriedade X e o Set acessador de propriedade Y usam a MyBase palavra-chave para acessar as propriedades herdadas. A declaração de propriedade Z sobrepõe-se à MustOverride propriedade - assim, não há membros pendentes MustOverride na classe B, e B é permitido ser uma classe regular.

As propriedades podem ser usadas para atrasar a inicialização de um recurso até o momento em que ele é referenciado pela primeira vez. Por exemplo:

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

A ConsoleStreams classe contém três propriedades, In, Out, e Error, que representam os dispositivos padrão de entrada, saída e erro, respectivamente. Ao expor esses membros como propriedades, a classe ConsoleStreams pode atrasar a sua inicialização até que eles sejam realmente usados. Por exemplo, ao fazer a primeira referência à Out propriedade, como em ConsoleStreams.Out.WriteLine("hello, world"), o subjacente TextWriter para o dispositivo de saída é inicializado. Mas se o aplicativo não fizer referência às In propriedades e Error , nenhum objeto será criado para esses dispositivos.

Obter declarações de acesso

Um Get acessador (getter) é declarado usando uma declaração de propriedade Get . Uma declaração de propriedade Get consiste na palavra-chave Get seguida por um bloco de instrução. Dada uma propriedade chamada P, uma Get declaração de acessador declara implicitamente um método com o nome get_P com os mesmos modificadores, tipo e lista de parâmetros que a propriedade. Se o tipo contiver uma declaração com esse nome, um erro em tempo de compilação resultará, mas a declaração implícita será ignorada para fins de vinculação de nome.

Uma variável local especial, que é implicitamente declarada no espaço de declaração do Get corpo do acessador com o mesmo nome da propriedade, representa o valor de retorno da propriedade. A variável local tem semântica de resolução de nome especial quando usada em expressões. Se a variável local for usada em um contexto que espera uma expressão classificada como um grupo de métodos, como uma expressão de invocação, o nome será resolvido para a função em vez de para a variável local. Por exemplo:

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

O uso de parênteses pode causar situações ambíguas (como F(1) onde F está uma propriedade cujo tipo é uma matriz unidimensional). Em todas as situações ambíguas, o nome é resolvido para a propriedade em vez da variável local. Por exemplo:

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

Quando o fluxo de controle deixa o corpo do Get acessador, o valor da variável local é passado de volta para a expressão de invocação. Como invocar um Get acessador é conceitualmente equivalente a ler o valor de uma variável, é considerado um estilo de programação ruim para Get os acessadores ter efeitos colaterais observáveis, como ilustrado no exemplo a seguir:

Class Counter
    Private Value As Integer

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

O valor da NextValue propriedade depende do número de vezes que a propriedade foi acessada anteriormente. Assim, acessar a propriedade produz um efeito colateral observável, e a propriedade deve, em vez disso, ser implementada como um método.

A convenção "sem efeitos colaterais" para Get acessadores não significa que Get os acessadores devem sempre ser escritos para simplesmente retornar valores armazenados em variáveis. De fato, Get os acessadores geralmente calculam o valor de uma propriedade acessando várias variáveis ou invocando métodos. No entanto, um acessador projetado Get corretamente não executa nenhuma ação que cause alterações observáveis no estado do objeto.

Nota. Get Os acessadores têm a mesma restrição de posicionamento de linha que as sub-rotinas. A instrução inicial, a instrução final e o bloco devem aparecer no início de uma linha lógica.

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

Definir declarações de acessador

Um Set acessador (setter) é declarado usando uma declaração de conjunto de propriedades. Uma declaração de conjunto de propriedades consiste na palavra-chave Set, uma lista de parâmetros opcional e um bloco de instrução. Dada uma propriedade chamada P, uma declaração setter declara implicitamente um método com o nome set_P com os mesmos modificadores e lista de parâmetros que a propriedade. Se o tipo contiver uma declaração com esse nome, um erro em tempo de compilação resultará, mas a declaração implícita será ignorada para fins de vinculação de nome.

Se uma lista de parâmetros for especificada, ela deve ter um membro, esse membro não deve ter modificadores exceto ByVal, e seu tipo deve ser o mesmo que o tipo da propriedade. O parâmetro representa o valor da propriedade que está sendo definida. Se o parâmetro for omitido, um parâmetro nomeado Value será declarado implicitamente.

Nota. Set Os acessadores têm a mesma restrição de posicionamento de linha que as sub-rotinas. A instrução inicial, a instrução final e o bloco devem aparecer no início de uma linha lógica.

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

Propriedades padrão

Uma propriedade que especifica o modificador Default é chamada de propriedade padrão. Qualquer tipo que permita propriedades pode ter uma propriedade padrão, incluindo interfaces. A propriedade padrão pode ser referenciada sem ter que qualificar a instância com o nome da propriedade. Assim, dada uma aula

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

o código

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

        y = x(10)
    End Sub
End Module

é equivalente a

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

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

Depois que uma propriedade é declarada Default, todas as propriedades sobrecarregadas nesse nome na hierarquia de herança se tornam a propriedade padrão, quer tenham sido declaradas Default ou não. Declarar uma propriedade Default em uma classe derivada quando a classe base declarou uma propriedade padrão por outro nome não requer nenhum outro modificador, como Shadows ou Overrides. Isso ocorre porque a propriedade padrão não tem identidade ou assinatura e, portanto, não pode ser sombreada ou sobrecarregada. Por exemplo:

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

Este programa produzirá a saída:

MoreDerived = 10
Derived = 10
Base = 10

Todas as propriedades padrão declaradas dentro de um tipo devem ter o mesmo nome e, para maior clareza, devem especificar o Default modificador. Como uma propriedade padrão sem parâmetros de índice causaria uma situação ambígua ao atribuir instâncias da classe que contém, as propriedades padrão devem ter parâmetros de índice. Além disso, se uma propriedade sobrecarregada em um nome específico incluir o Default modificador, todas as propriedades sobrecarregadas nesse nome deverão especificá-lo. As propriedades padrão podem não ser Shared, e pelo menos um acessador da propriedade não deve ser Private.

Propriedades implementadas automaticamente

Se uma propriedade omitir a declaração de quaisquer acessadores, uma implementação da propriedade será fornecida automaticamente, a menos que a propriedade seja declarada em uma interface ou seja declarada MustOverride. Somente propriedades de leitura/gravação sem argumentos podem ser implementadas automaticamente; caso contrário, ocorrerá um erro em tempo de compilação.

Uma propriedade ximplementada automaticamente, mesmo que uma substitua outra propriedade, introduz uma variável _x local privada com o mesmo tipo da propriedade. Se houver uma colisão entre o nome da variável local e outra declaração, um erro em tempo de compilação será relatado. O acessador da Get propriedade implementada automaticamente retorna o valor do local e o acessador da propriedade Set que define o valor do local. Por exemplo, a declaração:

Public Property x() As Integer

é aproximadamente equivalente a:

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

Assim como as declarações de variáveis, uma propriedade implementada automaticamente pode incluir um inicializador. Por exemplo:

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

Nota. Quando uma propriedade implementada automaticamente é inicializada, ela é inicializada através da propriedade, não do campo subjacente. Isso é para que as propriedades de substituição possam intercetar a inicialização, se necessário.

Os inicializadores de matriz são permitidos em propriedades implementadas automaticamente, exceto que não há como especificar os limites de matriz explicitamente. Por exemplo:

' 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

Propriedades do iterador

Uma propriedade iterator é uma propriedade com o Iterator modificador. É usado pela mesma razão que um método iterador (Section Iterator Methods) é usado -- como uma maneira conveniente de gerar uma sequência, que pode ser consumida For Each pela instrução. O Get acessador de uma propriedade iterator é interpretado da mesma maneira que um método iterator.

Uma propriedade iteradora deve ter um acessador explícito Get , e seu tipo deve ser IEnumerator, ou IEnumerable, ou IEnumerator(Of T)IEnumerable(Of T) para alguns T.

Aqui está um exemplo de uma propriedade iterator:

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

Operadores

Operadores são métodos que definem o significado de um operador existente do Visual Basic para a classe que contém. Quando o operador é aplicado à classe em uma expressão, o operador é compilado em uma chamada para o método de operador definido na classe. Definir um operador para uma classe também é conhecido como sobrecarregar o operador.

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'
    ;

Não é possível sobrecarregar um operador que já existe; Na prática, isto aplica-se principalmente aos operadores de conversão. Por exemplo, não é possível sobrecarregar a conversão de uma classe derivada para uma classe base:

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

Os operadores também podem ser sobrecarregados no sentido comum da palavra:

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

As declarações de operador não adicionam explicitamente nomes ao espaço de declaração do tipo que contém; no entanto, eles declaram implicitamente um método correspondente começando com os caracteres "op_". As seções a seguir listam os nomes de método correspondentes com cada operador.

Existem três classes de operadores que podem ser definidas: operadores unários, operadores binários e operadores de conversão. Todas as declarações de operador partilham certas restrições:

  • As declarações do operador devem ser Public sempre e Shared. O Public modificador pode ser omitido em contextos onde o modificador será assumido.

  • Os parâmetros de um operador não podem ser declarados ByRef, Optional ou ParamArray.

  • O tipo de pelo menos um dos operandos ou o valor de retorno deve ser o tipo que contém o operador.

  • Não há nenhuma variável de retorno de função definida para operadores. Portanto, a Return instrução deve ser usada para retornar valores de um corpo operador.

A única exceção a essas restrições se aplica a tipos de valor anuláveis. Como os tipos de valor anulável não têm uma definição de tipo real, um tipo de valor pode declarar operadores definidos pelo usuário para a versão anulável do tipo. Ao determinar se um tipo pode declarar um determinado operador definido pelo usuário, os ? modificadores são primeiro descartados de todos os tipos envolvidos na declaração para fins de verificação de validade. Essa flexibilização não se aplica ao tipo de retorno dos operadores IsTrueIsFalse , eles ainda devem voltar Boolean, não Boolean?.

A precedência e a associatividade de um operador não podem ser modificadas por uma declaração do operador.

Nota. Os operadores têm a mesma restrição de colocação de linha que as sub-rotinas. A instrução inicial, a instrução final e o bloco devem aparecer no início de uma linha lógica.

Operadores Unary

Os seguintes operadores unários podem ser sobrecarregados:

  • O operador + unário mais (método correspondente: op_UnaryPlus)

  • O operador - unário menos (método correspondente: op_UnaryNegation)

  • O operador lógico Not (método correspondente: op_OnesComplement)

  • Os IsTrue e IsFalse operadores (métodos correspondentes: op_True, op_False)

Todos os operadores unários sobrecarregados devem tomar um único parâmetro do tipo que contém e podem retornar qualquer tipo, exceto para IsTrue e IsFalse, que deve retornar Boolean. Se o tipo que contém é um tipo genérico, os parâmetros de tipo devem corresponder aos parâmetros de tipo do tipo que contém. Por exemplo

Structure Complex
    ...

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

Se um tipo sobrecarrega um ou IsTrueIsFalse, então ele deve sobrecarregar o outro também. Se apenas um estiver sobrecarregado, um erro em tempo de compilação resulta.

Nota. IsTrue e IsFalse não são palavras reservadas.

Operadores binários

Os seguintes operadores binários podem ser sobrecarregados:

  • Os operadores de adição, subtração-, multiplicação*, divisão/, divisão \integral, modulo Mod e exponenciação ^ (método correspondente: op_Addition, op_Subtraction, op_Multiply, op_Division, op_IntegerDivision, op_Exponentop_Modulus)+

  • Os operadores =relacionais , , <><, >, <=, ( >= métodos correspondentes: op_Equality, op_Inequality, op_LessThan, , op_LessThanOrEqualop_GreaterThan, op_GreaterThanOrEqual). Nota. Enquanto o operador de igualdade pode ser sobrecarregado, o operador de atribuição (usado apenas em instruções de atribuição) não pode ser sobrecarregado.

  • O Like operador (método correspondente: op_Like)

  • O operador & de concatenação (método correspondente: op_Concatenate)

  • O lógico And, Or e Xor operadores (métodos correspondentes: op_BitwiseAnd, op_BitwiseOr, op_ExclusiveOr)

  • Os operadores de turnos << e >> (métodos correspondentes: op_LeftShift, op_RightShift)

Todos os operadores binários sobrecarregados devem tomar o tipo de contenção como um dos parâmetros. Se o tipo que contém é um tipo genérico, os parâmetros de tipo devem corresponder aos parâmetros de tipo do tipo que contém. Os operadores de turno restringem ainda mais esta regra para exigir que o primeiro parâmetro seja do tipo que contém; o segundo parâmetro deve ser sempre do tipo Integer.

Os seguintes operadores binários devem ser declarados em pares:

  • Operador = e operador <>

  • Operador > e operador <

  • Operador >= e operador <=

Se um dos pares for declarado, o outro também deve ser declarado com parâmetros correspondentes e tipos de retorno, ou um erro em tempo de compilação resultará. (Observação. O objetivo de exigir declarações emparelhadas de operadores relacionais é tentar garantir pelo menos um nível mínimo de consistência lógica em operadores sobrecarregados.)

Em contraste com os operadores relacionais, sobrecarregar os operadores de divisão e de divisão integral é fortemente desencorajado, embora não seja um erro. (Observação. Em geral, os dois tipos de divisão devem ser totalmente distintos: um tipo que suporta a divisão é integral (caso em que deve apoiar \) ou não (caso em que deve apoiar /). Consideramos um erro definir ambos os operadores, mas como suas linguagens geralmente não distinguem entre dois tipos de divisão da mesma forma que o Visual Basic faz, sentimos que era mais seguro permitir a prática, mas fortemente desencorajá-la.)

Os operadores de atribuição compostos não podem ser sobrecarregados diretamente. Em vez disso, quando o operador binário correspondente estiver sobrecarregado, o operador de atribuição composto usará o operador sobrecarregado. Por exemplo:

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

Operadores de Conversão

Os operadores de conversão definem novas conversões entre tipos. Essas novas conversões são chamadas de conversões definidas pelo usuário. Um operador de conversão converte de um tipo de fonte, indicado pelo tipo de parâmetro do operador de conversão, para um tipo de destino, indicado pelo tipo de retorno do operador de conversão. As conversões devem ser classificadas como alargamento ou estreitamento. Uma declaração de operador de conversão que inclui a Widening palavra-chave introduz uma conversão de alargamento definida pelo usuário (método correspondente: op_Implicit). Uma declaração de operador de conversão que inclui a Narrowing palavra-chave introduz uma conversão de estreitamento definida pelo usuário (método correspondente: op_Explicit).

Em geral, as conversões de ampliação definidas pelo usuário devem ser projetadas para nunca lançar exceções e nunca perder informações. Se uma conversão definida pelo usuário pode causar exceções (por exemplo, porque o argumento de origem está fora do intervalo) ou perda de informações (como descartar bits de ordem alta), essa conversão deve ser definida como uma conversão de estreitamento. No exemplo:

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

A conversão de para Byte é uma conversão de Digit alargamento porque nunca lança exceções ou perde informações, mas a conversão de para Digit é uma conversão de Byte estreitamento, uma vez que Digit só pode representar um subconjunto dos valores possíveis de um Byte.

Ao contrário de todos os outros membros do tipo que podem ser sobrecarregados, a assinatura de um operador de conversão inclui o tipo de destino da conversão. Este é o único membro do tipo para o qual o tipo de retorno participa da assinatura. No entanto, a classificação de alargamento ou estreitamento de um operador de conversão não faz parte da assinatura do operador. Assim, uma classe ou estrutura não pode declarar um operador de conversão de alargamento e um operador de conversão de estreitamento com os mesmos tipos de origem e destino.

Um operador de conversão definido pelo usuário deve converter para ou do tipo que contém -- por exemplo, é possível que uma classe C defina uma conversão de C para Integer e de Integer para C, mas não de Integer para Boolean. Se o tipo que contém é um tipo genérico, os parâmetros de tipo devem corresponder aos parâmetros de tipo do tipo que contém. Além disso, não é possível redefinir uma conversão intrínseca (ou seja, não definida pelo utilizador). Como resultado, um tipo não pode declarar uma conversão quando:

  • O tipo de origem e o tipo de destino são os mesmos.

  • Tanto o tipo de origem quanto o tipo de destino não são o tipo que define o operador de conversão.

  • O tipo de origem ou o tipo de destino é um tipo de interface.

  • O tipo de origem e os tipos de destino são relacionados por herança (incluindo Object).

A única exceção a essas regras se aplica a tipos de valor anulável. Como os tipos de valor anulável não têm uma definição de tipo real, um tipo de valor pode declarar conversões definidas pelo usuário para a versão anulável do tipo. Ao determinar se um tipo pode declarar uma conversão específica definida pelo usuário, os ? modificadores são primeiro descartados de todos os tipos envolvidos na declaração para fins de verificação de validade. Assim, a seguinte declaração é válida porque S pode definir uma conversão de S para T:

Structure T
    ...
End Structure

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

No entanto, a seguinte declaração não é válida porque a estrutura S não pode definir uma conversão de S para S:

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

Mapeamento de operadores

Como o conjunto de operadores que o Visual Basic suporta pode não corresponder exatamente ao conjunto de operadores que outras linguagens no .NET Framework, alguns operadores são mapeados especialmente para outros operadores ao serem definidos ou usados. Mais especificamente:

  • A definição de um operador de divisão integral definirá automaticamente um operador de divisão regular (utilizável apenas de outros idiomas) que chamará o operador de divisão integral.

  • Sobrecarregando o Not, Ande Or os operadores sobrecarregarão apenas o operador bit a bit da perspetiva de outras linguagens que distinguem entre operadores lógicos e bitwise.

  • Uma classe que sobrecarrega apenas os operadores lógicos em uma linguagem que distingue entre operadores lógicos e bitwise (ou seja, uma linguagem que usa op_LogicalNot, op_LogicalAnd, e op_LogicalOr para Not, Ande , e , respectivamente Or) terá seus operadores lógicos mapeados nos operadores lógicos do Visual Basic. Se os operadores lógicos e bitwise estiverem sobrecarregados, apenas os operadores bitwise serão usados.

  • Sobrecarregar os operadores e >> sobrecarregará apenas os operadores assinados da perspetiva de outras linguagens que distinguem entre operadores de turno << assinados e não assinados.

  • Uma classe que sobrecarrega apenas um operador de turno não assinado terá o operador de turno não assinado mapeado no operador de turno correspondente do Visual Basic. Se um operador de turno não assinado e assinado estiver sobrecarregado, apenas o operador de turno assinado será usado.