Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
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:
OverridableeNotOverridablesão mutuamente exclusivos e não podem ser combinados.MustOverrideimplicaOverridable(e assim não pode especificá-lo) e não pode ser combinado comNotOverridable.NotOverridablenão pode ser combinado comOverridableouMustOverridee deve ser combinado comOverrides.OverridesimplicaOverridable(e assim não pode especificá-lo) e não pode ser combinado comMustOverride.
Há também restrições adicionais em métodos substituíveis:
Um
MustOverridemétodo pode não incluir um corpo do método ou umEndconstructo, pode não substituir outro método e só pode aparecer emMustInheritclasses.Se um método especificar
Overridese 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 especificarShadows.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 Friendmétodo em outro assembly que não temFriendacesso deve especificarProtected(nãoProtected Friend).Privateos métodos podem não serOverridable,NotOverridableouMustOverride, nem podem substituir outros métodos.Métodos em
NotInheritableclasses podem não ser declaradosOverridableouMustOverride.
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
ParamArraypode ser uma única expressão de um tipo que se expande para oParamArraytipo. Nesse caso, oParamArrayato é 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 doParamArray. Nesse caso, a invocação cria uma instância doParamArraytipo 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
WithEventsmodificador ou aMyBasepalavra-chave ou ouMyClassouMe, 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
XEventHandlerserá 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
Privatevariável de instância digitada como o delegado, chamadoXEvent.Dois métodos nomeados
add_Xeremove_Xque 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 àAddHandlerdeclaração.Um método chamado
remove_X, correspondente àRemoveHandlerdeclaração.Um método chamado
fire_X, correspondente àRaiseEventdeclaraçã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)ouSystem.TypedEventHandle(Of T, U), e não permitirão outros.O
XEventcampo tem o tipoSystem.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenTable(Of T)em queTestá 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
MeouMyClass.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
Getacessador e umSetacessador. Diz-se que a propriedade é uma propriedade de leitura e gravação.Se a propriedade especificar o
ReadOnlymodificador, a propriedade deverá ter umGetacessador e talvez não tenha umSetacessador. 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
WriteOnlymodificador, a propriedade deverá ter umSetacessador e talvez não tenha umGetacessador. 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 quePublic,Protected Friend,ProtectedouFriend.Friendé mais restritivo do queProtected FriendouPublic.Protectedé mais restritivo do queProtected FriendouPublic.Protected Friendé mais restritivo do quePublic.
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
PubliceShared. OPublicmodificador pode ser omitido em contextos em que o modificador será assumido.Os parâmetros de um operador não podem ser declarados
ByRefouOptionalParamArray.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
Returndeve 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
IsTrueoperadores eIsFalseos 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 moduloMode 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_GreaterThanOrEqualObservaçã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
Likeoperador (método correspondente:op_Like)O operador
&de concatenação (método correspondente:op_Concatenate)Os operadores lógicos
AndOreXorlógicos (métodos correspondentes:op_BitwiseAnd, ,op_BitwiseOr)op_ExclusiveOrOs 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,AndeOros 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 paraNot,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.
Visual Basic language spec