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 interface

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

ImplementsClause
    : ( 'Implements' ImplementsList )?
    ;

ImplementsList
    : InterfaceMemberSpecifier ( Comma InterfaceMemberSpecifier )*
    ;

InterfaceMemberSpecifier
    : NonArrayTypeName Period IdentifierOrKeyword
    ;

Métodos e propriedades que implementam membros da interface são implicitamente NotOverridable , a menos que declarados como MustOverride, Overridableou substituam outro membro. É um erro para um membro implementar um membro da interface.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 implementações 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 em implementação. 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 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 usará o tipo 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 de interface na lista de implementações é especificado usando um nome de tipo, um período e um identificador. O nome do tipo deve ser uma interface na lista de implementações ou uma interface base de uma interface na lista de implementações e o identificador deve ser um membro da interface especificada. Um único membro pode implementar mais de um membro de 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 em implementação deverá referenciar explicitamente uma interface base na qual o membro está disponível. Por exemplo, se I1 e I2 contiver um membroM, e I3 herdar de e I2, uma implementação I3 de I1 tipo implementará I1.M e I2.M. Se uma sombra de interface multiplicar membros herdados, um tipo de implementação terá que implementar os membros herdados e os membros que os sombream.

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 ser implementada for genérica, os mesmos argumentos de tipo que a interface que está sendo implementada deverão 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

Métodos

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. 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 (Pushe PopToString):

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 "controle de versão" de bibliotecas. Por exemplo, a 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, a v2 da biblioteca deseja adicionar outro parâmetro opcional "password" e deseja fazê-lo sem interromper a compatibilidade de origem (para que os aplicativos usados para direcionar v1 possam ser recompilados) e sem quebrar a compatibilidade binária (portanto, os aplicativos usados para fazer referência à v1 agora podem referenciar v2 sem recompilação). Esta é a aparência da v2:

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 regulares do método Async e Iterator

Há 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ó poderão ser omitidos se o método for definido em uma interface ou tiver o MustOverride modificador. Se nenhum tipo de retorno for especificado em uma função e a semântica estrita estiver sendo usada, ocorrerá um erro de 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 dos tipos de parâmetro de um método deve ser igual ou um superconjunto do domínio de acessibilidade do próprio método.

Um método regular é aquele que não tem nem AsyncIterator modificadores. Pode ser uma sub-rotina ou uma função. Os Métodos Regulares da Seção detalham o que acontece quando um método regular é invocado.

Um método iterador é aquele com o Iterator modificador e nenhum Async modificador. Deve ser uma função, e seu tipo de retorno deve ser IEnumerator, IEnumerableou ou IEnumerator(Of T)IEnumerable(Of T) para alguns T, e não ByRef deve ter parâmetros. Os Métodos do Iterador de Seção detalham o que acontece quando um método iterador é invocado.

Um método assíncrono é aquele 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 Te não ByRef deve ter parâmetros. Os Métodos Assíncronos da Seção detalham o que acontece quando um método assíncrono é invocado.

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

As declarações de sub-rotina e função são especiais, pois suas instruções de início e de término devem começar no início de uma linha lógica. Além disso, o corpo de uma declaração de função ou não-sub-rotinaMustOverride 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 apresenta um novo método cuja implementação é fornecida externa 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 ou End construção de método. Métodos externos são compartilhados implicitamente, podem não ter parâmetros de tipo e podem não manipular eventos ou implementar membros da interface. Se nenhum tipo de retorno for especificado em uma função e a semântica estrita estiver sendo usada, ocorrerá um erro de 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 dos tipos de parâmetro de um método externo deve ser igual ou um superconjunto do domínio de acessibilidade do próprio método externo.

A cláusula de biblioteca 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 único também pode ser especificado, que rege o conjunto de caracteres usado para realizar marshaling de cadeias de caracteres durante uma chamada para o método externo. O Unicode modificador faz marshaling de todas as cadeias de caracteres para valores Unicode, o Ansi modificador faz marshaling de todas as cadeias de caracteres para valores ANSI e o Auto modificador faz marshaling das 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 de nome do método dependerá 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, uma A consulta será acrescentada e a pesquisa tentará novamente. Se a plataforma for considerada Unicode (por exemplo, Windows NT, Windows 2000, Windows XP), um W será acrescentado e o nome será pesquisado. Se a pesquisa falhar, a pesquisa será tentada novamente sem o W. 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 marshalling 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 novamente no argumento de cadeia de caracteres. 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 de 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.

Determinadas combinações desses modificadores não são válidas:

  • Overridable e NotOverridable são mutuamente exclusivos e não podem ser combinados.

  • MustOverride implica Overridable (e assim 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 assim não pode especificá-lo) e não pode ser combinado com MustOverride.

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

  • Um MustOverride método pode não incluir um corpo do método ou um End constructo, pode não 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 de tempo de compilação. Um método de substituição pode não especificar Shadows.

  • Um método poderá não 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 que substitui 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 podem não ser Overridable, NotOverridableou MustOverride, nem podem substituir outros métodos.

  • Métodos em NotInheritable classes podem não 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 apresenta um método F e um Overridable método G. A classe Derived introduz um novo método F, sombreando assim o 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 do método real a ser invocada.

Métodos compartilhados

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 por meio de uma instância específica de um tipo. No entanto, é válido usar uma instância para qualificar um método compartilhado. É inválido se referir a Me, MyClassou MyBase em um método compartilhado. Métodos compartilhados podem não ser Overridable, NotOverridableou MustOverride, e eles podem não substituir métodos. Métodos definidos em módulos e interfaces padrão podem não especificar Shared, pois já estão implicitamente Shared .

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 de função de instância, um identificador pode ser usado para acessar membros da instância e membros compartilhados. O método G mostra que, em um membro de função compartilhada, é um erro acessar um membro de 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 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 a semântica estrita for usada, ocorrerá um erro de 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 incluir uma As cláusula, todos os parâmetros deverão especificar tipos.

Os parâmetros são especificados como parâmetros de valor, referência, opcional ou paramarray pelos modificadoresByVal, ByRefeOptionalParamArray, respectivamente. Um parâmetro que não especifica ByRef ou ByVal usa ByValcomo padrão .

Os nomes de parâmetro são definidos como 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ável aos parâmetros recém-criados. Como declarações de método externo e declarações de delegado não têm corpo, nomes de parâmetro 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 ele é anulável e também por modificadores de nome de matriz para indicar que é uma matriz. Eles 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 deverá estar presente.

Parâmetros de valor

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

Um método tem permissão para atribuir novos valores a um parâmetro de valor. Essas atribuições afetam apenas o local de armazenamento local representado pelo parâmetro de valor; eles não têm nenhum efeito sobre o argumento real fornecido na invocação do método.

Um parâmetro de valor é 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. Essa variável é inicializada copiando o valor do argumento correspondente. O exemplo a seguir mostra um método F que tem um parâmetro de valor 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 de valor 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 poderá não 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 fornecida como o argumento no método ou invocação do construtor. Conceitualmente, 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 por meio de copy-in copy-back.

Aliases. 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 se refere à variável do argumento correspondente. As modificações de um parâmetro de referência afetam diretamente 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 passa G uma referência para s ambos a e b. Assim, para essa invocação, os nomes sae 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 associada tardiamente, 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 ela for gravável) quando o método retornar. Portanto, 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 seja encerrado. 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, Fo 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 é d atribuído a ela e passado Fpara . Ao retornar, Fo 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 para trás não pode ser convertido Derived, uma exceção é gerada 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 formais também devem ser opcionais; A falha ao especificar o Optional modificador nos parâmetros a seguir disparará um erro de tempo de compilação. Um parâmetro opcional de algum tipo de tipo T? anulável 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 Objeto, o valor padrão do tipo de parâmetro será usado como o padrão para o parâmetro. Caso contrário, CType(e, T) deve ser uma expressão constante e é usada 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 próprio corpo do método.

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

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

Parâmetros paramArray

ParamArray os parâmetros são declarados com o ParamArray modificador. Se o ParamArray modificador estiver presente, o ByVal modificador deverá ser especificado e nenhum outro parâmetro poderá 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 .ParamArray 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 .ParamArray

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

  • O argumento fornecido para um ParamArray pode ser uma única expressão de um tipo que se expande para o ParamArray tipo. Nesse caso, o ParamArray ato é exatamente como um parâmetro de valor.

  • Como alternativa, a invocação pode especificar zero ou mais argumentos para o ParamArray, em que 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.

Com exceção de permitir um número variável de argumentos em uma invocação, um 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 F 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 valor. Da mesma forma, a terceira invocação cria F uma matriz de elemento zero e passa essa instância como um parâmetro de valor. A segunda e a terceira invocações são precisamente equivalentes à escrita:

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

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

Manipulação de eventos

Os métodos podem manipular declarativamente eventos gerados por objetos em variáveis compartilhadas ou de instância. Para lidar com 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 período:

  • O primeiro identificador deve ser uma instância ou variável compartilhada no tipo que contém que especifica o WithEvents modificador ou a MyBase palavra-chave ou ou MyClass ou Me , caso contrário, ocorrerá um erro de tempo de compilação. Essa variável contém o objeto que gerará os eventos tratados 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 resultado de erro.

Um método M de manipulador é considerado um manipulador de eventos válido para um evento E se a instrução AddHandler E, AddressOf M também for válida. No entanto, ao contrário de uma instrução AddHandler , os manipuladores de eventos explícitos permitem lidar com um evento com um método sem argumentos, independentemente de a 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 lidar com vários eventos correspondentes e vários métodos podem lidar com um único evento. A acessibilidade de um método não tem efeito sobre sua capacidade de lidar com eventos. O exemplo a seguir mostra como um método pode lidar com 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

Isso imprimirá:

Raised
Raised

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

Métodos de Extensão

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

Observação. 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. Desde que os métodos sigam as outras convenções descritas aqui e o tipo de contenção não seja um tipo genérico aberto e não possa 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 de tipo, 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 tenha métodos de extensão definidos nele ainda implementem apenas os membros declarados originalmente 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 acontece com métodos genéricos que não são de 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

Fazer referência a um tipo quando apenas um método de extensão no tipo estiver disponível ainda produzirá um erro de 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 associados, 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 é possível criar representantes 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

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

Observação. Um tipo de valor será colocado em caixa ao ser passado como um ByVal argumento para um parâmetro digitado como uma interface. Isso implica que os efeitos colaterais do método de extensão operarão em uma cópia da estrutura em vez do original. Embora o idioma não coloque nenhuma restrição no primeiro argumento de um método de extensão, é recomendável 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, embora não haja nenhum corpo fornecido no arquivo. A outra declaração parcial de MyForm então fornece a implementação do método.

Métodos parciais podem ser chamados independentemente de um corpo ter sido fornecido; se nenhum corpo do 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 passadas como argumentos para uma chamada de método parcial 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 um comportamento definido entre dois tipos parciais, pois os métodos parciais não terão custo se não forem usados.)

A declaração de método parcial deve ser declarada como Private e deve ser sempre uma sub-rotina sem instruções em seu corpo. Métodos parciais não podem implementar métodos de interface, embora o método que fornece 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 qualquer parâmetro de tipo, os mesmos modificadores de declaração e os mesmos nomes de parâmetro e parâmetro de tipo. Os atributos no método parcial e no método que fornece seu corpo são mesclados, assim como todos os 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

Construtores são métodos especiais que permitem o controle sobre a inicialização. Eles são executados após o início do programa 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; eles podem nunca ser invocados diretamente.

Observação. Os construtores têm a mesma restrição de posicionamento de linha que as sub-rotinas têm. 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

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 da 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, ela deverá ser a primeira instrução no corpo do método construtor. A instrução pode invocar outro construtor de instância do tipo, por exemplo, Me.New(...) ou MyClass.New(...) se não for uma estrutura, pode invocar um construtor de instância do tipo base, por exemplo. MyBase.New(...) É inválido que um construtor se invoque. Se um construtor omitir uma chamada para outro construtor, MyBase.New() estará implícito. Se não houver nenhum construtor de tipo base sem parâmetros, ocorrerá um erro de tempo de compilação. Como Me não é considerado como sendo construído até depois da chamada para um construtor de classe base, os parâmetros para uma instrução de invocação de construtor não podem referenciar Me, MyClassou MyBase implicitamente ou explicitamente.

Quando a primeira instrução de um construtor é do formulário MyBase.New(...), o construtor executa implicitamente as inicializações especificadas pelos inicializadores variáveis das variáveis de instância declaradas no tipo. Isso corresponde a uma sequência de atribuições 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ável 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 da Bseguinte saída, a seguinte saída é produzida:

x = 1, y = 1

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

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

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

Observação. 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

Portanto, o exemplo é exatamente equivalente ao seguinte:

Class Message
    Dim sender As Object
    Dim text As String

    Sub New()
    End Sub
End Class

Os construtores padrão emitidos em uma classe gerada pelo designer marcada com o atributo Microsoft.VisualBasic.CompilerServices.DesignerGeneratedAttribute chamarão o método Sub InitializeComponent(), se existir, após a chamada para o construtor base. (Observação. Isso permite que arquivos gerados pelo designer, como os criados pelo designer WinForms, omita o construtor no arquivo de designer. Isso permite que o programador especifique-o por conta própria, se assim desejar.)

Construtores compartilhados

Construtores compartilhados inicializam as variáveis compartilhadas de um tipo; eles são executados após o programa começar 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, nesse caso, 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 podem não chamar outros construtores. Antes da primeira instrução em um construtor compartilhado, o construtor compartilhado executa implicitamente as inicializações especificadas pelos inicializadores 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ável são executados na ordem textual 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 local conveniente para impor verificações em tempo de execução no parâmetro de tipo que não podem ser verificadas 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 de tipo é 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 depende principalmente da implementação, embora várias garantias sejam fornecidas se um construtor compartilhado for definido explicitamente:

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

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

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

As garantias acima não se aplicam à situação em que um construtor compartilhado é criado implicitamente para inicializadores compartilhados. A saída do exemplo a seguir é incerta, pois a ordenação 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 um dos seguintes:

Init A
A.F
Init B
B.F

or

Init B
Init A
A.F
B.F

Por outro lado, o exemplo a seguir produz uma saída previsível. Observe que o Shared construtor da classe A nunca é executado, mesmo que a classe B deriva dela:

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

Isso 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 classe B continua a calcular o valor inicial de Y, o que faz com que a classe A seja carregada recursivamente porque o valor é A.X referenciado. O Shared construtor de classe A , por sua vez, continua a calcular o valor inicial de X, e ao fazê-lo busca o valor padrão de Y, que é zero. A.X é, portanto, inicializado para 1. O processo de carregamento A , em seguida, é concluído, retornando ao cálculo do valor inicial de Y, cujo resultado se torna 2.

Se o Main método estivesse 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 na qual as classes que contêm essas 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 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 poderá não ter um tipo de retorno. Se uma lista de parâmetros for especificada, ela poderá não conter Optional ou ParamArray parâmetros. O domínio de acessibilidade dos tipos de parâmetro e/ou tipo de delegado deve ser o mesmo que, 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 nomeado 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 delegada aninhada corresponde à declaração do método e tem a mesma acessibilidade que o evento. Os atributos na lista de parâmetros se aplicam aos parâmetros da classe delegada.

  • Uma Private variável de instância digitada como o delegado, chamado 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 de tempo de compilação resultará e as declarações e remove_X implícitas add_X serão ignoradas para fins de associaçã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 à declaração a seguir

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 delegado para cada evento. Por exemplo, no exemplo a seguir, três tipos de delegado ocultos são criados, embora 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: estaticamente ou dinamicamente. O tratamento estático 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

O tratamento dinâmico de eventos é mais complexo 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 exemplo a seguir mostra uma classe Form1 que é adicionada Button1_Click como um manipulador de eventos para Button1o evento de 's Click :

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

    Private Button1 As Button = New Button()

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

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

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 o acompanhamento de manipuladores de eventos. Por exemplo, se uma classe definir quarenta eventos dos quais apenas alguns serão manipulados, usar uma tabela de hash em vez de quarenta campos para acompanhar os manipuladores de cada evento poderá ser mais eficiente. Eventos personalizados permitem que os métodos e remove_X os add_X métodos sejam definidos explicitamente, o que permite o armazenamento personalizado para manipuladores de eventos.

Eventos personalizados são declarados da mesma forma que os eventos que especificam um tipo delegado são declarados, com a exceção de que a palavra-chave Custom deve preceder a Event palavra-chave. Uma declaração de evento personalizada 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 a RemoveHandler declaração levam um ByVal parâmetro, que deve ser do tipo delegado do evento. Quando uma instrução ou RemoveHandler uma instrução AddHandler é executada (ou uma Handles cláusula manipula automaticamente um evento), a declaração correspondente será chamada. A RaiseEvent declaração usa os mesmos parâmetros que o delegado do evento e será chamada quando uma RaiseEvent instrução for executada. Todas as declarações devem ser fornecidas e são consideradas sub-rotinas.

Observe que AddHandler, RemoveHandler e RaiseEvent as declarações têm a mesma restrição de posicionamento de linha que as sub-rotinas têm. 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 nomeado 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 de tempo de compilação resultará e todas as declarações implícitas serão ignoradas para fins de associação de nome. Não é possível substituir ou sobrecarregar nenhum dos membros introduzidos, embora seja possível sombreá-los em tipos derivados.

Observação. Custom não é uma palavra reservada.

Eventos personalizados em assemblies 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 implementados em outro lugar, são tratados de forma um pouco diferente.

  • As ferramentas externas usadas para criar o winmd normalmente permitirão apenas determinados tipos 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) em que T está o tipo delegado.

  • O acessador AddHandler retorna um System.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokene 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 compartilhadas implicitamente. Se a declaração contiver uma As cláusula, a cláusula especifica o tipo do membro introduzido pela declaração. Se o tipo for omitido, o tipo da constante será inferido. O tipo de uma constante pode ser apenas um tipo primitivo ou Object. Se uma constante for digitada como Object e não houver nenhum tipo de caractere, 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 nomeada 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 por meio 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 simples. 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

Essa 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 igual ou um superconjunto do domínio de acessibilidade da constante em si. 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, em seguida Z, e por fim X, a produção dos 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 computado no 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 deverá ser especificado se nenhum modificador for especificado, mas poderá ser omitido de outra forma. Uma única declaração de variável pode incluir vários declaradores variáveis; cada declarador de variável apresenta 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

Essa 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 surge 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 tipo genérico fechado específico. 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 da variável à qual pertence. Em outras palavras, quando uma variável de um tipo de valor entra em existência ou deixa de existir, a variável de instância do tipo de valor também existe.

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

Observação. 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 tipo de elemento de matriz ou tipo de variável compartilhada deve ser igual 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 nomeadas 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

Variáveis de Read-Only

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 só podem ocorrer como parte da declaração ou em um construtor na mesma classe. Especificamente, as atribuições para uma instância somente leitura ou variável compartilhada são permitidas apenas nas seguintes situações:

  • Na declaração de variável que apresenta 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 maneira não qualificada ou por meio 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 computado em tempo de compilação por uma expressão constante.

Um exemplo do primeiro aplicativo desse tipo segue, no qual variáveis compartilhadas de cor são declaradas ReadOnly para impedir que elas 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ântica diferente. 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 o aplicativo a seguir, 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 denotar 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 no tempo de compilação, mas é obtida em tempo de execução. Portanto, se o valor for X 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 X teria sido obtido no momento Program2 foi compilado e teria permanecido inalterado por alterações até Program1Program2 que fosse recompilado.

Variáveis WithEvents

Um tipo pode declarar que ele manipula alguns conjuntos de eventos gerados por uma de suas instâncias ou variáveis 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 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 de evento. Por exemplo, se o nome da variável for F, ele será renomeado _F e uma propriedade F será declarada implicitamente. Se houver uma colisão entre o novo nome da variável e outra declaração, um erro de tempo de compilação será relatado. Todos os atributos aplicados à variável são levados para a variável renomeada.

A propriedade implícita criada por uma WithEvents declaração cuida de conectar e desabilitar 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 (desafocando 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 pode não ser especificado em uma estrutura e WithEventsReadOnly não pode ser combinado.

Inicializadores de variável

Declarações de instância e variável compartilhada em classes e declarações de variável de instância (mas não declarações de variável compartilhada) em estruturas podem incluir inicializadores 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, as variáveis inicializadores correspondem a instruções de atribuição executadas quando uma instância da classe é criada. As estruturas não podem ter inicializadores de variável 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 ocorrer quando a classe é carregada e atribui e is ocorre quando uma nova instância da classe é criada.

É útil pensar em inicializadores 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, em que 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 para 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 é inicializada automaticamente 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 se referir a uma variável que será inicializada após ele, nesse caso, o valor da variável referenciada é seu valor padrão para as finalidades do inicializador. Esse inicializador é de valor duvidoso.

Há 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. Somente 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, somente uma única variável poderá ser declarada por 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 atribuindo o resultado da expressão de criação de objeto à variável. So

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.

Inicializadores Array-Size

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 conversíveis para 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 especificados. O número de dimensões do tipo de matriz é inferido do inicializador de tamanho da matriz. So

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 vã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 em uma matriz com limites na 0..5 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

Classes derivadas da classe System.MarshalByRefObject são marshaladas entre limites de contexto usando proxies (ou seja, por referência) em vez de copiar (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 faz marshaling de acessos variáveis e chamadas de método em um limite de contexto.

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

Há uma exceção a essa regra: um membro implicitamente ou explicitamente qualificado Me está isento das restrições acima, pois Me sempre é garantido que seja um objeto real, não um proxy.

Propriedades

As propriedades são uma extensão natural de variáveis; ambos são membros nomeados com tipos associados e a sintaxe para acessar variáveis e propriedades é a mesma. No entanto, ao contrário das variáveis, 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 se assemelha a uma declaração de campo. A segunda parte inclui um Get acessador 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 a semântica estrita estiver sendo usada, ocorrerá um erro de 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 for definida em uma interface ou definida com o MustOverride modificador, o corpo da propriedade e o End constructo deverão ser omitidos; caso contrário, ocorrerá um erro de tempo de compilação.

A lista de parâmetros de índice compõe a assinatura da propriedade, portanto, as propriedades podem ser sobrecarregadas em parâmetros de índice, mas não no tipo da propriedade. A lista de parâmetros de índice é a mesma de 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).

Uma propriedade pode ser declarada da seguinte maneira:

  • 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 e gravação.

  • Se a propriedade especificar o ReadOnly modificador, a propriedade deverá ter um Get acessador e talvez não tenha um Set acessador. Diz-se que a propriedade é somente leitura. É um erro de tempo de compilação uma propriedade somente leitura ser o destino de uma atribuição.

  • Se a propriedade especificar o WriteOnly modificador, a propriedade deverá ter um Set acessador e talvez não tenha um Get acessador. Diz-se que a propriedade é somente gravação. É um erro de tempo de compilação referenciar 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 acessadores e Set os acessadores de uma propriedade não são membros distintos e não é possível declarar os acessadores de uma propriedade 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 de tempo de compilação.

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

  • Private é mais restritivo do que Public, Protected Friend, Protectedou Friend.

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

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

  • Protected Friend é mais restritivo 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 sombreia uma propriedade, a propriedade derivada oculta a propriedade sombreada em relação à leitura e à escrita. No exemplo a seguir, a P propriedade oculta B 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 dos tipos de parâmetro deve ser igual ou um superconjunto do domínio de acessibilidade da própria propriedade. Uma propriedade pode ter apenas um Set acessador e um Get acessador.

Exceto pelas diferenças na sintaxe de declaração e invocação, Overridable, NotOverridable, , Overridese MustInheritMustOverrideas propriedades se comportam exatamente comoOverridable, NotOverridable, , Overridese MustOverrideMustInherit 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 está uma Overridable propriedade somente leitura, Y é uma Overridable propriedade de leitura-gravação e Z é uma MustOverride propriedade de 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

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

Por outro lado, uma classe derivada 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 as propriedades base são substituídas. Cada declaração de propriedade corresponde exatamente aos modificadores de acessibilidade, ao tipo e ao nome da propriedade herdada correspondente. O Get acessador de propriedade X e o Set acessador da propriedade Y usam a MyBase palavra-chave para acessar as propriedades herdadas. A declaração de propriedade Z substitui a MustOverride propriedade , portanto, não há membros pendentes MustOverride na classe Be B tem permissão para 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 ConsoleStreams classe pode atrasar sua inicialização até que eles sejam realmente usados. Por exemplo, ao referenciar primeiro a Out propriedade, como em ConsoleStreams.Out.WriteLine("hello, world"), o subjacente TextWriter para o dispositivo de saída é inicializado. No entanto, se o aplicativo não fizer referência às In propriedades e Error , em seguida, nenhum objeto será criado para esses dispositivos.

Obter declarações do acessador

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 de tempo de compilação resultará, mas a declaração implícita será ignorada para fins de associação de nome.

Uma variável local especial, que é declarada implicitamente no Get espaço de declaração do corpo do acessador com o mesmo nome da propriedade, representa o valor retornado 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 e não 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 à leitura do valor de uma variável, é considerado um estilo de programação ruim para Get os acessadores terem efeitos colaterais observáveis, conforme 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, em vez disso, a propriedade deve ser implementada como um método.

A convenção "sem efeitos colaterais" para Get acessadores não significa que Get os acessadores sempre devem ser gravados 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.

Observação. Get os acessadores têm a mesma restrição de posicionamento de linha que as sub-rotinas têm. 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 do 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 de tempo de compilação resultará, mas a declaração implícita será ignorada para fins de associação de nome.

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

Observação. Set os acessadores têm a mesma restrição de posicionamento de linha que as sub-rotinas têm. 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 precisar qualificar a instância com o nome da propriedade. Assim, dada uma classe

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 tornam-se a propriedade padrão, independentemente de terem 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 em 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á-la. As propriedades padrão podem não ser Sharede pelo menos um acessador da propriedade não deve ser Private.

Propriedades implementadas automaticamente

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

Uma propriedade ximplementada automaticamente, mesmo uma substituindo outra propriedade, introduz uma variável _x local privada com o mesmo tipo que a propriedade. Se houver uma colisão entre o nome da variável local e outra declaração, um erro de tempo de compilação será relatado. O acessador da Get propriedade implementada automaticamente retorna o valor do acessador local e da Set propriedade 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 acontece com declarações 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" }

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

Inicializadores de matriz são permitidos em propriedades implementadas automaticamente, exceto que não há como especificar explicitamente os limites da matriz. 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 iterador é uma propriedade com o Iterator modificador. Ele é usado pelo mesmo motivo pelo qual um método iterador ( Métodos de Iterador de Seção) é usado -- como uma maneira conveniente de gerar uma sequência, que pode ser consumida pela For Each instrução. O Get acessador de uma propriedade de iterador é interpretado da mesma forma que um método iterador.

Uma propriedade iterador deve ter um acessador explícitoGet, e seu tipo deve serIEnumerator, ouIEnumerable, ou ou IEnumerable(Of T)IEnumerator(Of T) para algum T.

Aqui está um exemplo de uma propriedade iterador:

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

Os operadores são métodos que definem o significado de um operador do Visual Basic existente 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, isso se aplica 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 senso 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 do 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.

Há 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 compartilham determinadas restrições:

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

  • Os parâmetros de um operador não podem ser declarados ByRefou OptionalParamArray.

  • O tipo de pelo menos um dos operandos ou o valor retornado 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 instrução Return deve ser usada para retornar valores de um corpo do operador.

A única exceção a essas restrições se aplica a tipos de valor anuláveis. Como os tipos de valor anuláveis 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 removidos pela primeira vez de todos os tipos envolvidos na declaração para fins de verificação de validade. Esse relaxamento não se aplica ao tipo de retorno e operadores IsTrueIsFalse ; eles ainda devem retornar Boolean, não Boolean?.

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

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

Operadores unários

Os seguintes operadores unários podem ser sobrecarregados:

  • O operador + de adição unário (método correspondente: op_UnaryPlus)

  • O operador - unário de subtração (método correspondente: op_UnaryNegation)

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

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

Todos os operadores unários sobrecarregados devem usar um único parâmetro do tipo que contém e podem retornar qualquer tipo, exceto e IsTrueIsFalse, que deve retornar Boolean. Se o tipo de contenção for um tipo genérico, os parâmetros de tipo deverão 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, em seguida, ele deve sobrecarregar o outro também. Se apenas um estiver sobrecarregado, um erro de tempo de compilação resultará.

Observação. IsTrue e IsFalse não são palavras reservadas.

Operadores binários

Os seguintes operadores binários podem ser sobrecarregados:

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

  • Os operadores relacionais =, <>, , <, >, >=<=(métodos correspondentes: op_Equality, , op_Inequality, op_LessThan, op_GreaterThan, op_LessThanOrEqual, ). op_GreaterThanOrEqual Observação. Embora o operador de igualdade possa 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)

  • Os operadores lógicos AndOr e Xor lógicos (métodos correspondentes: op_BitwiseAnd, , op_BitwiseOr) op_ExclusiveOr

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

Todos os operadores binários sobrecarregados devem usar o tipo de contenção como um dos parâmetros. Se o tipo de contenção for um tipo genérico, os parâmetros de tipo deverão corresponder aos parâmetros de tipo do tipo que contém. Os operadores de deslocamento restringem ainda mais essa 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 deverá ser declarado com tipos de parâmetro e de retorno correspondentes, ou um erro de tempo de compilação resultará. (Observação. A finalidade 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.)

Ao contrário dos operadores relacionais, sobrecarregar os operadores de divisão e divisão integral é altamente desencorajado, embora não seja um erro. (Observação. Em geral, os dois tipos de divisão devem ser totalmente distintos: um tipo que dá suporte à divisão é integral (nesse caso, deve dar suporte \) ou não (nesse caso, ele deve dar suporte /). Consideramos que é um erro definir ambos os operadores, mas como seus idiomas geralmente não distinguem entre dois tipos de divisão da maneira que o Visual Basic faz, achamos mais seguro permitir a prática, mas desanunciá-la fortemente.)

Os operadores de atribuição composta não podem ser sobrecarregados diretamente. Em vez disso, quando o operador binário correspondente for sobrecarregado, o operador de atribuição composta 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 origem, 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 ampliação ou estreitamento. Uma declaração de operador de conversão que inclui a Widening palavra-chave introduz uma conversão de ampliação 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 restrição 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 gerar exceções e nunca perder informações. Se uma conversão definida pelo usuário puder causar exceções (por exemplo, porque o argumento de origem está fora do intervalo) ou perda de informações (como descartar bits de alta ordem), essa conversão deverá ser definida como uma conversão de restrição. 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 Digit para Byte é uma conversão de ampliação porque nunca gera exceções ou perde informações, mas a conversão de Byte para Digit é uma conversão de restrição Digit , pois só pode representar um subconjunto dos valores possíveis de um Byte.

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

Um operador de conversão definido pelo usuário deve converter de ou para o tipo que contém , por exemplo, é possível que uma classe C defina uma conversão de CInteger e para CInteger , mas não de Integer para Boolean. Se o tipo de contenção for um tipo genérico, os parâmetros de tipo deverão 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 usuário). Como resultado, um tipo não pode declarar uma conversão em que:

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

  • O tipo de origem e 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 estão relacionados por herança (incluindo Object).

A única exceção a essas regras se aplica a tipos de valor anuláveis. Como os tipos de valor anuláveis 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 definida pelo usuário específica, os ? modificadores são removidos pela primeira vez de todos os tipos envolvidos na declaração para fins de verificação de validade. Portanto, a declaração a seguir é válida porque S pode definir uma conversão de S :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 declaração a seguir não é válida porque a estrutura S não pode definir uma conversão de S :S

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

Mapeamento de operador

Como o conjunto de operadores aos quais o Visual Basic dá suporte pode não corresponder exatamente ao conjunto de operadores que outros idiomas no .NET Framework, alguns operadores são mapeados especialmente para outros operadores ao serem definidos ou usados. Especificamente:

  • Definir um operador de divisão integral definirá automaticamente um operador de divisão regular (utilizável somente de outros idiomas) que chamará o operador de divisão integral.

  • Sobrecarregar o Notoperador, Ande Or os operadores sobrecarregarão apenas o operador bit a bit da perspectiva de outras linguagens que distinguem entre operadores lógicos e bit a bit.

  • Uma classe que sobrecarrega apenas os operadores lógicos em uma linguagem que distingue entre operadores lógicos e bit a bit (ou seja, uma linguagem que usa op_LogicalNot, op_LogicalAnde para Not, AndeOrop_LogicalOr, respectivamente) terá seus operadores lógicos mapeados para os operadores lógicos do Visual Basic. Se os operadores lógicos e bit a bit estiverem sobrecarregados, somente os operadores bit a bit serão usados.

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

  • Uma classe que sobrecarrega apenas um operador de turno sem sinal terá o operador de turno sem sinal mapeado para o operador de turno do Visual Basic correspondente. Se um operador de turno sem sinal e assinado estiver sobrecarregado, somente o operador de turno assinado será usado.