Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. 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 de interface
Métodos, eventos e propriedades podem implementar membros da interface. Para implementar um membro da interface, uma declaração de membro especifica a palavra-chave e lista um ou mais membros da Implements interface.
ImplementsClause
: ( 'Implements' ImplementsList )?
;
ImplementsList
: InterfaceMemberSpecifier ( Comma InterfaceMemberSpecifier )*
;
InterfaceMemberSpecifier
: NonArrayTypeName Period IdentifierOrKeyword
;
Os métodos e propriedades que implementam membros da interface são implicitamente NotOverridable , a menos que sejam declarados como MustOverride, Overridableou substituam outro membro. É um erro para um membro que implementa um membro da interface ser Shared. A acessibilidade de um membro não tem efeito sobre sua capacidade de implementar membros da interface.
Para que uma implementação de interface seja válida, a lista de implementos do tipo que contém deve nomear uma interface que contenha um membro compatível. Um membro compatível é aquele cuja assinatura corresponde à assinatura do membro implementador. Se uma interface genérica estiver sendo implementada, o argumento type fornecido na cláusula Implements será substituído na assinatura ao verificar a compatibilidade. Por exemplo:
Interface I1(Of T)
Sub F(x As T)
End Interface
Class C1
Implements I1(Of Integer)
Sub F(x As Integer) Implements I1(Of Integer).F
End Sub
End Class
Class C2(Of U)
Implements I1(Of U)
Sub F(x As U) Implements I1(Of U).F
End Sub
End Class
Se um evento declarado usando um tipo de delegado estiver implementando um evento de interface, um evento compatível será aquele cujo tipo de delegado subjacente é do mesmo tipo. Caso contrário, o evento usa o tipo de delegado do evento de interface que está implementando. Se esse evento implementar vários eventos de interface, todos os eventos de interface deverão ter o mesmo tipo de delegado subjacente. Por exemplo:
Interface ClickEvents
Event LeftClick(x As Integer, y As Integer)
Event RightClick(x As Integer, y As Integer)
End Interface
Class Button
Implements ClickEvents
' OK. Signatures match, delegate type = ClickEvents.LeftClickHandler.
Event LeftClick(x As Integer, y As Integer) _
Implements ClickEvents.LeftClick
' OK. Signatures match, delegate type = ClickEvents.RightClickHandler.
Event RightClick(x As Integer, y As Integer) _
Implements ClickEvents.RightClick
End Class
Class Label
Implements ClickEvents
' Error. Signatures match, but can't be both delegate types.
Event Click(x As Integer, y As Integer) _
Implements ClickEvents.LeftClick, ClickEvents.RightClick
End Class
Um membro da interface na lista de implementos é especificado usando um nome de tipo, um ponto e um identificador. O nome do tipo deve ser uma interface na lista implements ou uma interface base de uma interface na lista implements, e o identificador deve ser um membro da interface especificada. Um único membro pode implementar mais de um membro da interface correspondente.
Interface ILeft
Sub F()
End Interface
Interface IRight
Sub F()
End Interface
Class Test
Implements ILeft, IRight
Sub F() Implements ILeft.F, IRight.F
End Sub
End Class
Se o membro da interface que está sendo implementado não estiver disponível em todas as interfaces explicitamente implementadas devido à herança de várias interfaces, o membro implementador deverá fazer referência explícita a uma interface base na qual o membro está disponível. Por exemplo, se I1 e I2 contiver um membro M, e I3 herdar de I1 e I2, um tipo de implementação I3 implementará I1.M e I2.M. Se uma interface sombrear membros herdados, um tipo de implementação terá que implementar os membros herdados e o(s) membro(s) sombreando-os.
Interface ILeft
Sub F()
End Interface
Interface IRight
Sub F()
End Interface
Interface ILeftRight
Inherits ILeft, IRight
Shadows Sub F()
End Interface
Class Test
Implements ILeftRight
Sub LeftF() Implements ILeft.F
End Sub
Sub RightF() Implements IRight.F
End Sub
Sub LeftRightF() Implements ILeftRight.F
End Sub
End Class
Se a interface que contém o membro da interface a ser implementado for genérica, os mesmos argumentos de tipo que a interface que está sendo implementada devem ser fornecidos. Por exemplo:
Interface I1(Of T)
Function F() As T
End Interface
Class C1
Implements I1(Of Integer)
Implements I1(Of Double)
Function F1() As Integer Implements I1(Of Integer).F
End Function
Function F2() As Double Implements I1(Of Double).F
End Function
' Error: I1(Of String) is not implemented by C1
Function F3() As String Implements I1(Of String).F
End Function
End Class
Class C2(Of U)
Implements I1(Of U)
Function F() As U Implements I1(Of U).F
End Function
End Class
Metodologia
Os métodos contêm as instruções executáveis de um programa.
MethodMemberDeclaration
: MethodDeclaration
| ExternalMethodDeclaration
;
InterfaceMethodMemberDeclaration
: InterfaceMethodDeclaration
;
MethodDeclaration
: SubDeclaration
| MustOverrideSubDeclaration
| FunctionDeclaration
| MustOverrideFunctionDeclaration
;
InterfaceMethodDeclaration
: InterfaceSubDeclaration
| InterfaceFunctionDeclaration
;
SubSignature
: 'Sub' Identifier TypeParameterList?
( OpenParenthesis ParameterList? CloseParenthesis )?
;
FunctionSignature
: 'Function' Identifier TypeParameterList?
( OpenParenthesis ParameterList? CloseParenthesis )?
( 'As' Attributes? TypeName )?
;
SubDeclaration
: Attributes? ProcedureModifier* SubSignature
HandlesOrImplements? LineTerminator
Block
'End' 'Sub' StatementTerminator
;
MustOverrideSubDeclaration
: Attributes? MustOverrideProcedureModifier+ SubSignature
HandlesOrImplements? StatementTerminator
;
InterfaceSubDeclaration
: Attributes? InterfaceProcedureModifier* SubSignature StatementTerminator
;
FunctionDeclaration
: Attributes? ProcedureModifier* FunctionSignature
HandlesOrImplements? LineTerminator
Block
'End' 'Function' StatementTerminator
;
MustOverrideFunctionDeclaration
: Attributes? MustOverrideProcedureModifier+ FunctionSignature
HandlesOrImplements? StatementTerminator
;
InterfaceFunctionDeclaration
: Attributes? InterfaceProcedureModifier* FunctionSignature StatementTerminator
;
ProcedureModifier
: AccessModifier | 'Shadows' | 'Shared' | 'Overridable' | 'NotOverridable' | 'Overrides'
| 'Overloads' | 'Partial' | 'Iterator' | 'Async'
;
MustOverrideProcedureModifier
: ProcedureModifier
| 'MustOverride'
;
InterfaceProcedureModifier
: 'Shadows' | 'Overloads'
;
HandlesOrImplements
: HandlesClause
| ImplementsClause
;
Os métodos, que têm uma lista opcional de parâmetros e um valor de retorno opcional, são compartilhados ou não compartilhados. Os métodos compartilhados são acessados por meio da classe ou instâncias da classe. Métodos não compartilhados, também chamados de métodos de instância, são acessados por meio de instâncias da classe. O exemplo a seguir mostra uma classe Stack que tem vários métodos compartilhados (Clone e Flip), e vários métodos de instância (Push, Pop, e ToString):
Public Class Stack
Public Shared Function Clone(s As Stack) As Stack
...
End Function
Public Shared Function Flip(s As Stack) As Stack
...
End Function
Public Function Pop() As Object
...
End Function
Public Sub Push(o As Object)
...
End Sub
Public Overrides Function ToString() As String
...
End Function
End Class
Module Test
Sub Main()
Dim s As Stack = New Stack()
Dim i As Integer
While i < 10
s.Push(i)
End While
Dim flipped As Stack = Stack.Flip(s)
Dim cloned As Stack = Stack.Clone(s)
Console.WriteLine("Original stack: " & s.ToString())
Console.WriteLine("Flipped stack: " & flipped.ToString())
Console.WriteLine("Cloned stack: " & cloned.ToString())
End Sub
End Module
Os métodos podem ser sobrecarregados, o que significa que vários métodos podem ter o mesmo nome, desde que tenham assinaturas exclusivas. A assinatura de um método consiste no número e tipos de seus parâmetros. A assinatura de um método especificamente não inclui o tipo de retorno ou modificadores de parâmetro, como Optional, ByRef ou ParamArray. O exemplo a seguir mostra uma classe com várias sobrecargas:
Module Test
Sub F()
Console.WriteLine("F()")
End Sub
Sub F(o As Object)
Console.WriteLine("F(Object)")
End Sub
Sub F(value As Integer)
Console.WriteLine("F(Integer)")
End Sub
Sub F(a As Integer, b As Integer)
Console.WriteLine("F(Integer, Integer)")
End Sub
Sub F(values() As Integer)
Console.WriteLine("F(Integer())")
End Sub
Sub G(s As String, Optional s2 As String = 5)
Console.WriteLine("G(String, Optional String")
End Sub
Sub G(s As String)
Console.WriteLine("G(String)")
End Sub
Sub Main()
F()
F(1)
F(CType(1, Object))
F(1, 2)
F(New Integer() { 1, 2, 3 })
G("hello")
G("hello", "world")
End Sub
End Module
A saída do programa é:
F()
F(Integer)
F(Object)
F(Integer, Integer)
F(Integer())
G(String)
G(String, Optional String)
Sobrecargas que diferem apenas em parâmetros opcionais podem ser usadas para "versionamento" de bibliotecas. Por exemplo, v1 de uma biblioteca pode incluir uma função com parâmetros opcionais:
Sub fopen(fileName As String, Optional accessMode as Integer = 0)
Em seguida, v2 da biblioteca quer adicionar outro parâmetro opcional "senha", e quer fazê-lo sem quebrar a compatibilidade de origem (para que os aplicativos que costumavam visar v1 podem ser recompilados), e sem quebrar a compatibilidade binária (para que os aplicativos que costumavam fazer referência v1 agora podem referenciar v2 sem recompilação). É assim que a v2 ficará:
Sub fopen(file As String, mode as Integer)
Sub fopen(file As String, Optional mode as Integer = 0, Optional pword As String = "")
Observe que os parâmetros opcionais em uma API pública não são compatíveis com CLS. No entanto, eles podem ser consumidos pelo menos pelo Visual Basic e C#4 e F#.
Declarações de método regular, assíncrono e iterador
Existem dois tipos de métodos: sub-rotinas, que não retornam valores, e funções, que retornam. O corpo e End a construção de um método só podem ser omitidos se o método for definido numa interface ou tiver o MustOverride modificador. Se nenhum tipo de retorno for especificado em uma função e semântica estrita estiver sendo usada, ocorrerá um erro em tempo de compilação; caso contrário, o tipo é implicitamente Object ou o tipo do caractere de tipo do método. O domínio de acessibilidade do tipo de retorno e os tipos de parâmetros de um método devem ser iguais ou um superconjunto do domínio de acessibilidade do próprio método.
Um método regular é aquele sem modificadores AsyncIterator . Pode ser uma sub-rotina ou uma função. A seção Métodos regulares detalha o que acontece quando um método regular é invocado.
Um método iterador é um com o Iterator modificador e nenhum Async modificador. Deve ser uma função, e seu tipo de retorno deve ser IEnumerator, IEnumerableou IEnumerator(Of T)IEnumerable(Of T) para alguns T, e não deve ter ByRef parâmetros. Section Iterator Methods detalha o que acontece quando um método iterador é invocado.
Um método assíncrono é um com o Async modificador e nenhum Iterator modificador. Deve ser uma sub-rotina, ou uma função com tipo Task de retorno ou Task(Of T) para alguns T, e não deve ter ByRef parâmetros. Seção Métodos assíncronos detalha o que acontece quando um método assíncrono é invocado.
É um erro em tempo de compilação se um método não é um desses três tipos de método.
As declarações de sub-rotina e função são especiais na medida em que suas instruções de início e fim devem começar no início de uma linha lógica. Além disso, o corpo de uma declaração não-sub-rotinaMustOverride ou função deve começar no início de uma linha lógica. Por exemplo:
Module Test
' Illegal: Subroutine doesn't start the line
Public x As Integer : Sub F() : End Sub
' Illegal: First statement doesn't start the line
Sub G() : Console.WriteLine("G")
End Sub
' Illegal: End Sub doesn't start the line
Sub H() : End Sub
End Module
Declarações de método externo
Uma declaração de método externo introduz um novo método cuja implementação é fornecida externamente ao programa.
ExternalMethodDeclaration
: ExternalSubDeclaration
| ExternalFunctionDeclaration
;
ExternalSubDeclaration
: Attributes? ExternalMethodModifier* 'Declare' CharsetModifier? 'Sub'
Identifier LibraryClause AliasClause?
( OpenParenthesis ParameterList? CloseParenthesis )? StatementTerminator
;
ExternalFunctionDeclaration
: Attributes? ExternalMethodModifier* 'Declare' CharsetModifier? 'Function'
Identifier LibraryClause AliasClause?
( OpenParenthesis ParameterList? CloseParenthesis )?
( 'As' Attributes? TypeName )?
StatementTerminator
;
ExternalMethodModifier
: AccessModifier
| 'Shadows'
| 'Overloads'
;
CharsetModifier
: 'Ansi' | 'Unicode' | 'Auto'
;
LibraryClause
: 'Lib' StringLiteral
;
AliasClause
: 'Alias' StringLiteral
;
Como uma declaração de método externo não fornece nenhuma implementação real, ela não tem nenhum corpo de método ou End construção. Os métodos externos são implicitamente compartilhados, podem não ter parâmetros de tipo e não podem manipular eventos ou implementar membros da interface. Se nenhum tipo de retorno for especificado em uma função e semântica estrita estiver sendo usada, ocorrerá um erro em tempo de compilação. Caso contrário, o tipo é implicitamente Object ou o tipo do caractere de tipo do método. O domínio de acessibilidade do tipo de retorno e os tipos de parâmetros de um método externo devem ser iguais ou um superconjunto do domínio de acessibilidade do próprio método externo.
A cláusula library de uma declaração de método externo especifica o nome do arquivo externo que implementa o método. A cláusula de alias opcional é uma cadeia de caracteres que especifica o ordinal numérico (prefixado por um # caractere) ou o nome do método no arquivo externo. Um modificador de conjunto de caracteres únicos também pode ser especificado, que governa o conjunto de caracteres usado para organizar cadeias de caracteres durante uma chamada para o método externo. O Unicode modificador marshals todas as cadeias de caracteres para valores Unicode, o Ansi modificador marshals todas as cadeias de caracteres para valores ANSI e o Auto modificador marshals as cadeias de caracteres de acordo com as regras do .NET Framework com base no nome do método ou no nome do alias, se especificado. Se nenhum modificador for especificado, o padrão será Ansi.
Se Ansi ou Unicode for especificado, o nome do método será pesquisado no arquivo externo sem nenhuma modificação. Se Auto for especificado, a pesquisa do nome do método depende da plataforma. Se a plataforma for considerada ANSI (por exemplo, Windows 95, Windows 98, Windows ME), o nome do método será pesquisado sem nenhuma modificação. Se a pesquisa falhar, um A é anexado e a pesquisa tentada novamente. Se a plataforma for considerada Unicode (por exemplo, Windows NT, Windows 2000, Windows XP), então a W é anexada e o nome é pesquisado. Se a pesquisa falhar, a pesquisa será tentada novamente sem o Warquivo . Por exemplo:
Module Test
' All platforms bind to "ExternSub".
Declare Ansi Sub ExternSub Lib "ExternDLL" ()
' All platforms bind to "ExternSub".
Declare Unicode Sub ExternSub Lib "ExternDLL" ()
' ANSI platforms: bind to "ExternSub" then "ExternSubA".
' Unicode platforms: bind to "ExternSubW" then "ExternSub".
Declare Auto Sub ExternSub Lib "ExternDLL" ()
End Module
Os tipos de dados que estão sendo passados para métodos externos são empacotados de acordo com as convenções de empacotamento de dados do .NET Framework com uma exceção. As variáveis de cadeia de caracteres que são passadas por valor (ou seja, ByVal x As String) são empacotadas para o tipo BSTR de automação OLE, e as alterações feitas no BSTR no método externo são refletidas de volta no argumento string. Isso ocorre porque o tipo String em métodos externos é mutável, e esse marshalling especial imita esse comportamento. Os parâmetros de cadeia de caracteres que são passados por referência (ou seja, ByRef x As String) são empacotados como um ponteiro para o tipo BSTR de automação OLE. É possível substituir esses comportamentos especiais especificando o System.Runtime.InteropServices.MarshalAsAttribute atributo no parâmetro.
O exemplo demonstra o uso de métodos externos:
Class Path
Declare Function CreateDirectory Lib "kernel32" ( _
Name As String, sa As SecurityAttributes) As Boolean
Declare Function RemoveDirectory Lib "kernel32" ( _
Name As String) As Boolean
Declare Function GetCurrentDirectory Lib "kernel32" ( _
BufSize As Integer, Buf As String) As Integer
Declare Function SetCurrentDirectory Lib "kernel32" ( _
Name As String) As Boolean
End Class
Métodos substituíveis
O Overridable modificador indica que um método é substituível. O Overrides modificador indica que um método substitui um método substituível do tipo base que tem a mesma assinatura. O NotOverridable modificador indica que um método substituível não pode ser substituído. O MustOverride modificador indica que um método deve ser substituído em classes derivadas.
Certas combinações destes modificadores não são válidas:
OverridableeNotOverridableexcluem-se mutuamente e não podem ser combinados.MustOverrideimplicaOverridable(e, portanto, não pode especificá-lo) e não pode ser combinado comNotOverridable.NotOverridablenão pode ser combinado comOverridableouMustOverridee deve ser combinado comOverrides.OverridesimplicaOverridable(e, portanto, não pode especificá-lo) e não pode ser combinado comMustOverride.
Há também restrições adicionais sobre métodos substituíveis:
Um
MustOverridemétodo não pode incluir um corpo de método ou umaEndconstrução, não pode 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 em tempo de compilação. Um método de substituição pode não especificarShadows.Um método não pode substituir outro método se o domínio de acessibilidade do método de substituição não for igual ao domínio de acessibilidade do método que está sendo substituído. A única exceção é que um método substituindo um
Protected Friendmétodo em outro assembly que não temFriendacesso deve especificarProtected(nãoProtected Friend).Privateos métodos não podem serOverridable,NotOverridableouMustOverride, nem podem sobrepor-se a outros métodos.Os métodos das
NotInheritableclasses não podem 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 introduz um método F e um Overridable método G. A classe Derived introduz um novo método F, sombreando assim o método herdado F, e também substitui o método Gherdado. O exemplo produz a seguinte saída:
Base.F
Derived.F
Derived.G
Derived.G
Observe que a instrução b.G() invoca Derived.G, não Base.G. Isso ocorre porque o tipo de tempo de execução da instância (que é Derived) em vez do tipo de tempo de compilação da instância (que é Base) determina a implementação real do método a ser invocada.
Métodos Partilhados
O Shared modificador indica que um método é um método compartilhado. Um método compartilhado não opera em uma instância específica de um tipo e pode ser invocado diretamente de um tipo em vez de através de uma instância específica de um tipo. É válido, no entanto, usar uma instância para qualificar um método compartilhado. É inválido referir-se a Me, MyClassou MyBase em um método compartilhado. Os métodos partilhados não podem ser Overridable, NotOverridableou MustOverride, e não podem substituir os métodos. Os métodos definidos em módulos e interfaces padrão podem não especificar Shared, porque eles já estão implicitamente Shared já.
Um método declarado em uma estrutura ou classe sem um Shared modificador é um método de instância. Um método de instância opera em uma determinada instância de um tipo. Os métodos de instância só podem ser invocados por meio de uma instância de um tipo e podem se referir à instância por meio da Me expressão.
O exemplo a seguir ilustra as regras para acessar membros compartilhados e de instância:
Class Test
Private x As Integer
Private Shared y As Integer
Sub F()
x = 1 ' Ok, same as Me.x = 1.
y = 1 ' Ok, same as Test.y = 1.
End Sub
Shared Sub G()
x = 1 ' Error, cannot access Me.x.
y = 1 ' Ok, same as Test.y = 1.
End Sub
Shared Sub Main()
Dim t As Test = New Test()
t.x = 1 ' Ok.
t.y = 1 ' Ok.
Test.x = 1 ' Error, cannot access instance member through type.
Test.y = 1 ' Ok.
End Sub
End Class
O método F mostra que, em um membro da função de instância, um identificador pode ser usado para acessar membros da instância e membros compartilhados. Método G mostra que, em um membro de função compartilhada, é um erro acessar um membro da instância por meio de um identificador. O método Main mostra que, em uma expressão de acesso de membro, os membros da instância devem ser acessados por meio de instâncias, mas os membros compartilhados podem ser acessados por meio de tipos ou instâncias.
Parâmetros do método
Um parâmetro é uma variável que pode ser usada para passar informações para dentro e para fora de um método. Os parâmetros de um método são declarados pela lista de parâmetros do método, que consiste em um ou mais parâmetros separados por vírgulas.
ParameterList
: Parameter ( Comma Parameter )*
;
Parameter
: Attributes? ParameterModifier* ParameterIdentifier ( 'As' TypeName )?
( Equals ConstantExpression )?
;
ParameterModifier
: 'ByVal' | 'ByRef' | 'Optional' | 'ParamArray'
;
ParameterIdentifier
: Identifier IdentifierModifiers
;
Se nenhum tipo for especificado para um parâmetro e semântica estrita for usada, ocorrerá um erro em tempo de compilação. Caso contrário, o tipo padrão é Object ou o tipo do caractere de tipo do parâmetro. Mesmo sob semântica permissiva, se um parâmetro inclui uma As cláusula, todos os parâmetros devem especificar tipos.
Os parâmetros são especificados ByValcomo parâmetros de valor, referência, opcional ou paramarray pelos modificadores , , ByRefOptional, e ParamArray, respectivamente. Um parâmetro que não especifica ByRef ou ByVal assume como ByValpadrão .
Os nomes de parâmetros têm escopo para todo o corpo do método e são sempre acessíveis publicamente. Uma invocação de método cria uma cópia, específica para essa invocação, dos parâmetros, e a lista de argumentos da invocação atribui valores ou referências de variáveis aos parâmetros recém-criados. Como as declarações de método externo e as declarações delegadas não têm corpo, nomes de parâmetros duplicados são permitidos em listas de parâmetros, mas desencorajados.
O identificador pode ser seguido pelo modificador ? de nome anulável para indicar que é anulável e também por modificadores de nome de matriz para indicar que é uma matriz. Podem ser combinados, por exemplo, "ByVal x?() As Integer". Não é permitido usar limites de matriz explícitos; Além disso, se o modificador de nome anulável estiver presente, uma As cláusula deve estar presente.
Parâmetros de valor
Um parâmetro value é declarado com um modificador explícito ByVal . Se o ByVal modificador for usado, o ByRef modificador não pode ser especificado. Um parâmetro value surge com a invocação do membro ao qual o parâmetro pertence e é inicializado com o valor do argumento dado na invocação. Um parâmetro value deixa de existir após o retorno do membro.
Permite-se a um método atribuir novos valores a um parâmetro de valor. Tais atribuições afetam apenas o local de armazenamento local representado pelo parâmetro value; eles não têm efeito sobre o argumento real dado na invocação do método.
Um parâmetro value é usado quando o valor de um argumento é passado para um método e as modificações do parâmetro não afetam o argumento original. Um parâmetro de valor refere-se à sua própria variável, que é distinta da variável do argumento correspondente. Esta variável é inicializada copiando o valor do argumento correspondente. O exemplo a seguir mostra um método F que tem um parâmetro value chamado p:
Module Test
Sub F(p As Integer)
Console.WriteLine("p = " & p)
p += 1
End Sub
Sub Main()
Dim a As Integer = 1
Console.WriteLine("pre: a = " & a)
F(a)
Console.WriteLine("post: a = " & a)
End Sub
End Module
O exemplo produz a seguinte saída, mesmo que o parâmetro p value seja modificado:
pre: a = 1
p = 1
post: a = 1
Parâmetros de referência
Um parâmetro de referência é um parâmetro declarado com um ByRef modificador. Se o ByRef modificador for especificado, o ByVal modificador não pode ser usado. Um parâmetro de referência não cria um novo local de armazenamento. Em vez disso, um parâmetro de referência representa a variável dada como o argumento no método ou invocação do construtor. Conceptualmente, o valor de um parâmetro de referência é sempre o mesmo que a variável subjacente.
Os parâmetros de referência atuam em dois modos, como aliases ou através de copy-in copy-back.
Pseudónimos. Um parâmetro de referência é usado quando o parâmetro atua como um alias para um argumento fornecido pelo chamador. Um parâmetro de referência não define uma variável em si, mas refere-se à variável do argumento correspondente. As modificações de um parâmetro de referência afetam direta e imediatamente o argumento correspondente. O exemplo a seguir mostra um método Swap que tem dois parâmetros de referência:
Module Test
Sub Swap(ByRef a As Integer, ByRef b As Integer)
Dim t As Integer = a
a = b
b = t
End Sub
Sub Main()
Dim x As Integer = 1
Dim y As Integer = 2
Console.WriteLine("pre: x = " & x & ", y = " & y)
Swap(x, y)
Console.WriteLine("post: x = " & x & ", y = " & y)
End Sub
End Module
A saída do programa é:
pre: x = 1, y = 2
post: x = 2, y = 1
Para a invocação do método Swap na classe Main, a representa x, e b representa y. Assim, a invocação tem o efeito de trocar os valores de x e y.
Em um método que usa parâmetros de referência, é possível que vários nomes representem o mesmo local de armazenamento:
Module Test
Private s As String
Sub F(ByRef a As String, ByRef b As String)
s = "One"
a = "Two"
b = "Three"
End Sub
Sub G()
F(s, s)
End Sub
End Module
No exemplo, a invocação do método F em G passa uma referência para s ambos e ab. Assim, para essa invocação, os nomes s, ae b todos se referem ao mesmo local de armazenamento, e as três atribuições modificam a variável sde instância .
Copy-in copy-back. Se o tipo da variável que está sendo passada para um parâmetro de referência não for compatível com o tipo do parâmetro de referência, ou se uma não-variável (por exemplo, uma propriedade) for passada como um argumento para um parâmetro de referência, ou se a invocação for tardia, então uma variável temporária será alocada e passada para o parâmetro de referência. O valor que está sendo passado será copiado para essa variável temporária antes que o método seja invocado e será copiado de volta para a variável original (se houver uma e se for gravável) quando o método retornar. Assim, um parâmetro de referência pode não conter necessariamente uma referência ao armazenamento exato da variável que está sendo passada, e quaisquer alterações no parâmetro de referência podem não ser refletidas na variável até que o método saia. Por exemplo:
Class Base
End Class
Class Derived
Inherits Base
End Class
Module Test
Sub F(ByRef b As Base)
b = New Base()
End Sub
Property G() As Base
Get
End Get
Set
End Set
End Property
Sub Main()
Dim d As Derived
F(G) ' OK.
F(d) ' Throws System.InvalidCastException after F returns.
End Sub
End Module
No caso da primeira invocação de F, uma variável temporária é criada e o valor da propriedade G é atribuído a ela e passado para F. Após o retorno de F, o valor na variável temporária é atribuído de volta à propriedade de G. No segundo caso, outra variável temporária é criada e o valor de d é atribuído a ela e passado para F. Ao retornar de F, o valor na variável temporária é convertido de volta para o tipo da variável Derivede atribuído a d. Como o valor que está sendo passado de volta não pode ser convertido para Derived, uma exceção é lançada em tempo de execução.
Parâmetros opcionais
Um parâmetro opcional é declarado com o Optional modificador. Os parâmetros que seguem um parâmetro opcional na lista de parâmetros formal também devem ser opcionais; A falha ao especificar o Optional modificador nos seguintes parâmetros desencadeará um erro em tempo de compilação. Um parâmetro opcional de algum tipo tipo anulável T? ou tipo T não anulável deve especificar uma expressão e constante a ser usada como um valor padrão se nenhum argumento for especificado. Se e for avaliado como Nothing do tipo Object, o valor padrão do tipo de parâmetro será usado como padrão para o parâmetro. Caso contrário, CType(e, T) deve ser uma expressão constante e é tomado como o padrão para o parâmetro.
Parâmetros opcionais são a única situação em que um inicializador em um parâmetro é válido. A inicialização é sempre feita como parte da expressão de invocação, não dentro do corpo do método em si.
Module Test
Sub F(x As Integer, Optional y As Integer = 20)
Console.WriteLine("x = " & x & ", y = " & y)
End Sub
Sub Main()
F(10)
F(30,40)
End Sub
End Module
A saída do programa é:
x = 10, y = 20
x = 30, y = 40
Os parâmetros opcionais não podem ser especificados em declarações de delegado ou de evento, nem em expressões lambda.
Parâmetros ParamArray
ParamArray parâmetros são declarados com o ParamArray modificador. Se o ParamArray modificador estiver presente, o ByVal modificador deve ser especificado, e nenhum outro parâmetro pode usar o ParamArray modificador. O ParamArray tipo do parâmetro deve ser uma matriz unidimensional e deve ser o último parâmetro na lista de parâmetros.
Um ParamArray parâmetro representa um número indeterminado de parâmetros do tipo ParamArraydo . Dentro do próprio método, um ParamArray parâmetro é tratado como seu tipo declarado e não tem semântica especial. Um ParamArray parâmetro é implicitamente opcional, com um valor padrão de uma matriz unidimensional vazia do tipo ParamArraydo .
A ParamArray permite que os argumentos sejam especificados de uma de duas maneiras em uma invocação de método:
O argumento dado para um
ParamArraypode ser uma única expressão de um tipo que se amplia para oParamArraytipo. Neste caso, oParamArrayatua precisamente como um parâmetro de valor.Como alternativa, a invocação pode especificar zero ou mais argumentos para o
ParamArray, onde cada argumento é uma expressão de um tipo que é implicitamente conversível para o tipo de elemento 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.
Exceto por permitir um número variável de argumentos em uma invocação, a ParamArray é precisamente equivalente a um parâmetro de valor do mesmo tipo, como ilustra o exemplo a seguir.
Module Test
Sub F(ParamArray args() As Integer)
Dim i As Integer
Console.Write("Array contains " & args.Length & " elements:")
For Each i In args
Console.Write(" " & i)
Next i
Console.WriteLine()
End Sub
Sub Main()
Dim a As Integer() = { 1, 2, 3 }
F(a)
F(10, 20, 30, 40)
F()
End Sub
End Module
O exemplo produz a saída
Array contains 3 elements: 1 2 3
Array contains 4 elements: 10 20 30 40
Array contains 0 elements:
A primeira invocação de F simplesmente passa a matriz a como um parâmetro de valor. A segunda invocação de cria automaticamente uma matriz de quatro elementos com os valores de elemento fornecidos e passa essa instância de matriz como um parâmetro de F valor. Da mesma forma, a terceira invocação de cria uma matriz de elemento zero e passa essa instância como um parâmetro de F valor. A segunda e terceira invocações equivalem precisamente à escrita:
F(New Integer() {10, 20, 30, 40})
F(New Integer() {})
ParamArray Os parâmetros não podem ser especificados em declarações de delegado ou de evento.
Tratamento de eventos
Os métodos podem manipular declarativamente eventos gerados por objetos em instância ou variáveis compartilhadas. Para manipular eventos, uma declaração de método especifica a Handles palavra-chave e lista um ou mais eventos.
HandlesClause
: ( 'Handles' EventHandlesList )?
;
EventHandlesList
: EventMemberSpecifier ( Comma EventMemberSpecifier )*
;
EventMemberSpecifier
: Identifier Period IdentifierOrKeyword
| 'MyBase' Period IdentifierOrKeyword
| 'MyClass' Period IdentifierOrKeyword
| 'Me' Period IdentifierOrKeyword
;
Um evento na Handles lista é especificado por dois identificadores separados por um ponto:
O primeiro identificador deve ser uma instância ou variável compartilhada no tipo que contém que especifica o
WithEventsmodificador ou a palavra-chave ouMeMyClassouMyBase, caso contrário, ocorre um erro em tempo de compilação. Essa variável contém o objeto que gerará os eventos manipulados por esse método.O segundo identificador deve especificar um membro do tipo do primeiro identificador. O membro deve ser um evento, e pode ser compartilhado. Se uma variável compartilhada for especificada para o primeiro identificador, o evento deverá ser compartilhado ou um erro resultará.
Um método manipulador é considerado um manipulador M de eventos válido para um evento E se a instrução AddHandler E, AddressOf M também for válida. Ao contrário de uma AddHandler instrução, no entanto, manipuladores de eventos explícitos permitem manipular um evento com um método sem argumentos, independentemente de semântica estrita estar sendo usada ou não:
Option Strict On
Class C1
Event E(x As Integer)
End Class
Class C2
withEvents C1 As New C1()
' Valid
Sub M1() Handles C1.E
End Sub
Sub M2()
' Invalid
AddHandler C1.E, AddressOf M1
End Sub
End Class
Um único membro pode manipular vários eventos correspondentes e vários métodos podem manipular um único evento. A acessibilidade de um método não tem efeito sobre sua capacidade de manipular eventos. O exemplo a seguir mostra como um método pode manipular eventos:
Class Raiser
Event E1()
Sub Raise()
RaiseEvent E1
End Sub
End Class
Module Test
WithEvents x As Raiser
Sub E1Handler() Handles x.E1
Console.WriteLine("Raised")
End Sub
Sub Main()
x = New Raiser()
x.Raise()
x.Raise()
End Sub
End Module
Isto irá imprimir:
Raised
Raised
Um tipo herda todos os manipuladores de eventos fornecidos por seu tipo base. Um tipo derivado não pode de forma alguma alterar os mapeamentos de eventos que herda de seus tipos base, mas pode adicionar manipuladores adicionais ao evento.
Métodos da Extensão
Os métodos podem ser adicionados a tipos de fora da declaração de tipo usando métodos de extensão. Métodos de extensão são métodos com o System.Runtime.CompilerServices.ExtensionAttribute atributo aplicado a eles. Eles só podem ser declarados em módulos padrão e devem ter pelo menos um parâmetro, que especifica o tipo que o método estende. Por exemplo, o seguinte método de extensão estende o tipo String:
Imports System.Runtime.CompilerServices
Module StringExtensions
<Extension> _
Sub Print(s As String)
Console.WriteLine(s)
End Sub
End Module
Nota. Embora o Visual Basic exija que os métodos de extensão sejam declarados em um módulo padrão, outras linguagens como C# podem permitir que eles sejam declarados em outros tipos de tipos. Contanto que os métodos sigam as outras convenções descritas aqui e o tipo que contém não é um tipo genérico aberto e não pode ser instanciado, o Visual Basic reconhecerá os métodos de extensão.
Quando um método de extensão é invocado, a instância em que ele está sendo invocado é passada para o primeiro parâmetro. O primeiro parâmetro não pode ser declarado Optional ou ParamArray. Qualquer tipo, incluindo um parâmetro type, pode aparecer como o primeiro parâmetro de um método de extensão. Por exemplo, os seguintes métodos estendem os tipos Integer(), qualquer tipo que implementa System.Collections.Generic.IEnumerable(Of T)e qualquer tipo:
Imports System.Runtime.CompilerServices
Module Extensions
<Extension> _
Sub PrintArray(a() As Integer)
...
End Sub
<Extension> _
Sub PrintList(Of T)(a As IEnumerable(Of T))
...
End Sub
<Extension> _
Sub Print(Of T)(a As T)
...
End Sub
End Module
Como mostra o exemplo anterior, as interfaces podem ser estendidas. Os métodos de extensão de interface fornecem a implementação do método, de modo que os tipos que implementam uma interface que tem métodos de extensão definidos nela ainda implementam apenas os membros originalmente declarados pela interface. Por exemplo:
Imports System.Runtime.CompilerServices
Interface IAction
Sub DoAction()
End Interface
Module IActionExtensions
<Extension> _
Public Sub DoAnotherAction(i As IAction)
i.DoAction()
End Sub
End Module
Class C
Implements IAction
Sub DoAction() Implements IAction.DoAction
...
End Sub
' ERROR: Cannot implement extension method IAction.DoAnotherAction
Sub DoAnotherAction() Implements IAction.DoAnotherAction
...
End Sub
End Class
Os métodos de extensão também podem ter restrições de tipo em seus parâmetros de tipo e, assim como com métodos genéricos sem extensão, o argumento de tipo pode ser inferido:
Imports System.Runtime.CompilerServices
Module IEnumerableComparableExtensions
<Extension> _
Public Function Sort(Of T As IComparable(Of T))(i As IEnumerable(Of T)) _
As IEnumerable(Of T)
...
End Function
End Module
Os métodos de extensão também podem ser acessados por meio de expressões de instância implícitas dentro do tipo que está sendo estendido:
Imports System.Runtime.CompilerServices
Class C1
Sub M1()
Me.M2()
M2()
End Sub
End Class
Module C1Extensions
<Extension>
Sub M2(c As C1)
...
End Sub
End Module
Para fins de acessibilidade, os métodos de extensão também são tratados como membros do módulo padrão em que são declarados - eles não têm acesso extra aos membros do tipo que estão estendendo além do acesso que têm em virtude de seu contexto de declaração.
Os métodos de extensões só estão disponíveis quando o método de módulo padrão está no escopo. Caso contrário, o tipo original não parecerá ter sido estendido. Por exemplo:
Imports System.Runtime.CompilerServices
Class C1
End Class
Namespace N1
Module C1Extensions
<Extension> _
Sub M1(c As C1)
...
End Sub
End Module
End Namespace
Module Test
Sub Main()
Dim c As New C1()
' Error: c has no member named "M1"
c.M1()
End Sub
End Module
Referir-se a um tipo quando apenas um método de extensão no tipo está disponível ainda produzirá um erro em tempo de compilação.
É importante observar que os métodos de extensão são considerados membros do tipo em todos os contextos em que os membros estão vinculados, como o padrão fortemente tipado For Each . Por exemplo:
Imports System.Runtime.CompilerServices
Class C1
End Class
Class C1Enumerator
ReadOnly Property Current() As C1
Get
...
End Get
End Property
Function MoveNext() As Boolean
...
End Function
End Class
Module C1Extensions
<Extension> _
Function GetEnumerator(c As C1) As C1Enumerator
...
End Function
End Module
Module Test
Sub Main()
Dim c As New C1()
' Valid
For Each o As Object In c
...
Next o
End Sub
End Module
Também podem ser criados delegados que se referem a métodos de extensão. Assim, o código:
Delegate Sub D1()
Module Test
Sub Main()
Dim s As String = "Hello, World!"
Dim d As D1
d = AddressOf s.Print
d()
End Sub
End Module
é aproximadamente equivalente a:
Delegate Sub D1()
Module Test
Sub Main()
Dim s As String = "Hello, World!"
Dim d As D1
d = CType([Delegate].CreateDelegate(GetType(D1), s, _
GetType(StringExtensions).GetMethod("Print")), D1)
d()
End Sub
End Module
Nota. Visual Basic normalmente insere uma verificação em uma chamada de método de instância que faz com que a ocorra se System.NullReferenceException a instância em que o método está sendo invocado é Nothing. No caso de métodos de extensão, não há uma maneira eficiente de inserir essa verificação, portanto, os métodos de extensão precisarão verificar explicitamente se há Nothing.
Nota. Um tipo de valor será encaixotado ao ser passado como um ByVal argumento para um parâmetro digitado como uma interface. Isto implica que os efeitos secundários do método de extensão funcionarão numa cópia da estrutura em vez do original. Embora a linguagem não coloque restrições no primeiro argumento de um método de extensão, recomenda-se que os métodos de extensão não sejam usados para estender tipos de valor ou que, ao estender tipos de valor, o primeiro parâmetro seja passado ByRef para garantir que os efeitos colaterais operem no valor original.
Métodos parciais
Um método parcial é um método que especifica uma assinatura, mas não o corpo do método. O corpo do método pode ser fornecido por outra declaração de método com o mesmo nome e assinatura, provavelmente em outra declaração parcial do tipo. Por exemplo:
a.vb:
' Designer generated code
Public Partial Class MyForm
Private Partial Sub ValidateControls()
End Sub
Public Sub New()
' Initialize controls
...
ValidateControls()
End Sub
End Class
b.vb:
Public Partial Class MyForm
Public Sub ValidateControls()
' Validation logic goes here
...
End Sub
End Class
Neste exemplo, uma declaração parcial da classe MyForm declara um método ValidateControls parcial sem implementação. O construtor na declaração parcial chama o método parcial, mesmo que não haja nenhum corpo fornecido no arquivo. A outra declaração parcial fornece MyForm então a aplicação do método.
Métodos parciais podem ser chamados independentemente de um corpo ter sido fornecido; Se nenhum corpo de método for fornecido, a chamada será ignorada. Por exemplo:
Public Class C1
Private Partial Sub M1()
End Sub
Public Sub New()
' Since no implementation is supplied, this call will not be made.
M1()
End Sub
End Class
Todas as expressões que são passadas como argumentos para uma chamada de método parcial que é ignorada também são ignoradas e não avaliadas. (Observação. Isso significa que os métodos parciais são uma maneira muito eficiente de fornecer comportamento que é definido em dois tipos parciais, uma vez que os métodos parciais não têm custo se não forem usados.)
A declaração parcial do método deve ser declarada como Private e deve ser sempre uma sub-rotina sem declarações no seu corpo. Os métodos parciais não podem, por si só, implementar métodos de interface, embora o método que fornece o seu corpo possa.
Apenas um método pode fornecer um corpo a um método parcial. Um método que fornece um corpo a um método parcial deve ter a mesma assinatura que o método parcial, as mesmas restrições em quaisquer parâmetros de tipo, os mesmos modificadores de declaração e os mesmos nomes de parâmetros e parâmetros de tipo. Os atributos no método parcial e o método que fornece seu corpo são mesclados, assim como quaisquer atributos nos parâmetros dos métodos. Da mesma forma, a lista de eventos que os métodos manipulam é mesclada. Por exemplo:
Class C1
Event E1()
Event E2()
Private Partial Sub S() Handles Me.E1
End Sub
' Handles both E1 and E2
Private Sub S() Handles Me.E2
...
End Sub
End Class
Construtores
Os construtores são métodos especiais que permitem o controle sobre a inicialização. Eles são executados depois que o programa começa ou quando uma instância de um tipo é criada. Ao contrário de outros membros, os construtores não são herdados e não introduzem um nome no espaço de declaração de um tipo. Os construtores só podem ser invocados por expressões de criação de objeto ou pelo .NET Framework; nunca podem ser invocados diretamente.
Nota. Os construtores têm a mesma restrição de posicionamento de linha que as sub-rotinas. A instrução inicial, a instrução final e o bloco devem aparecer no início de uma linha lógica.
ConstructorMemberDeclaration
: Attributes? ConstructorModifier* 'Sub' 'New'
( OpenParenthesis ParameterList? CloseParenthesis )? LineTerminator
Block?
'End' 'Sub' StatementTerminator
;
ConstructorModifier
: AccessModifier
| 'Shared'
;
Construtores de instância
Os construtores de instância inicializam instâncias de um tipo e são executados pelo .NET Framework quando uma instância é criada. A lista de parâmetros de um construtor está sujeita às mesmas regras que a lista de parâmetros de um método. Os construtores de instância podem estar sobrecarregados.
Todos os construtores em tipos de referência devem invocar outro construtor. Se a invocação for explícita, deve ser a primeira instrução no corpo do método do construtor. A instrução pode invocar outro dos construtores de instância do tipo -- por exemplo, ou MyClass.New(...) -- ou, se não for uma estrutura, Me.New(...) pode invocar um construtor de instância do tipo base do tipo -- por exemplo, MyBase.New(...). É inválido para um construtor invocar a si mesmo. Se um construtor omite uma chamada para outro construtor, MyBase.New() está implícito. Se não houver nenhum construtor de tipo base sem parâmetros, ocorrerá um erro em tempo de compilação. Como Me não é considerado construído até depois da chamada para um construtor de classe base, os parâmetros para uma instrução de invocação do construtor não podem fazer referência Mea , MyClassou MyBase implícita ou explicitamente.
Quando a primeira instrução de um construtor é da forma MyBase.New(...), o construtor executa implicitamente as inicializações especificadas pelos inicializadores de variáveis de instância das variáveis de instância declaradas no tipo. Isso corresponde a uma sequência de atribuições que são executadas imediatamente após invocar o construtor de tipo base direto. Essa ordenação garante que todas as variáveis de instância base sejam inicializadas por seus inicializadores de variáveis antes que quaisquer instruções que tenham acesso à instância sejam executadas. Por exemplo:
Class A
Protected x As Integer = 1
End Class
Class B
Inherits A
Private y As Integer = x
Public Sub New()
Console.WriteLine("x = " & x & ", y = " & y)
End Sub
End Class
Quando New B() é usado para criar uma instância do , a seguinte saída é produzida B:
x = 1, y = 1
O valor de é 1 porque o inicializador de variável é executado depois que o construtor de y classe base é invocado. Os inicializadores de variáveis são executados na ordem textual em que aparecem na declaração de tipo.
Quando um tipo declara apenas Private construtores, não é possível, em geral, que outros tipos derivem do tipo ou criem instâncias do tipo, a única exceção são os tipos aninhados dentro do tipo.
Private Os construtores são comumente usados em tipos que contêm apenas Shared membros.
Se um tipo não contém declarações de construtor de instância, um construtor padrão é fornecido automaticamente. O construtor padrão simplesmente invoca o construtor sem parâmetros do tipo base direto. Se o tipo base direto não tiver um construtor sem parâmetros acessível, ocorrerá um erro em tempo de compilação. O tipo de acesso declarado para o construtor padrão é Public a menos que o tipo seja MustInherit, caso em que o construtor padrão é Protected.
Nota. O acesso padrão para o construtor padrão de um MustInherit tipo é Protected porque MustInherit as classes não podem ser criadas diretamente. Portanto, não faz sentido fazer o construtor Publicpadrão.
No exemplo a seguir, um construtor padrão é fornecido porque a classe não contém declarações de construtor:
Class Message
Dim sender As Object
Dim text As String
End Class
Assim, o exemplo é precisamente equivalente ao seguinte:
Class Message
Dim sender As Object
Dim text As String
Sub New()
End Sub
End Class
Os construtores padrão que são emitidos em uma classe gerada pelo designer marcada com o atributo Microsoft.VisualBasic.CompilerServices.DesignerGeneratedAttribute chamarão o método Sub InitializeComponent(), se ele existir, após a chamada para o construtor base. (Observação. Isso permite que os arquivos gerados pelo designer, como aqueles criados pelo designer WinForms, omitam o construtor no arquivo do designer. Isto permite ao programador especificá-lo por si próprio, se assim o desejar.)
Construtores compartilhados
Os construtores compartilhados inicializam as variáveis compartilhadas de um tipo; Eles são executados depois que o programa começa a ser executado, mas antes de qualquer referência a um membro do tipo. Um construtor compartilhado especifica o Shared modificador, a menos que esteja em um módulo padrão, caso em que o Shared modificador está implícito.
Ao contrário dos construtores de instância, os construtores compartilhados têm acesso público implícito, não têm parâmetros e não podem chamar outros construtores. Antes da primeira instrução em um construtor compartilhado, o construtor compartilhado executa implicitamente as inicializações especificadas pelos inicializadores de variáveis das variáveis compartilhadas declaradas no tipo. Isso corresponde a uma sequência de atribuições que são executadas imediatamente após a entrada no construtor. Os inicializadores de variáveis são executados na ordem textual em que aparecem na declaração de tipo.
O exemplo a seguir mostra uma Employee classe com um construtor compartilhado que inicializa uma variável compartilhada:
Imports System.Data
Class Employee
Private Shared ds As DataSet
Shared Sub New()
ds = New DataSet()
End Sub
Public Name As String
Public Salary As Decimal
End Class
Existe um construtor compartilhado separado para cada tipo genérico fechado. Como o construtor compartilhado é executado exatamente uma vez para cada tipo fechado, é um lugar conveniente para impor verificações em tempo de execução no parâmetro type que não pode ser verificado em tempo de compilação por meio de restrições. Por exemplo, o tipo a seguir usa um construtor compartilhado para impor que o parâmetro type é Integer ou Double:
Class EnumHolder(Of T)
Shared Sub New()
If Not GetType(T).IsEnum() Then
Throw New ArgumentException("T must be an enumerated type.")
End If
End Sub
End Class
Exatamente quando os construtores compartilhados são executados é principalmente dependente da implementação, embora várias garantias sejam fornecidas se um construtor compartilhado for explicitamente definido:
Os construtores compartilhados são executados antes do primeiro acesso a qualquer campo estático do tipo.
Os construtores compartilhados são executados antes da primeira invocação de qualquer método estático do tipo.
Os construtores compartilhados são executados antes da primeira invocação de qualquer construtor para o tipo.
As garantias acima não se aplicam na situação em que um construtor compartilhado é implicitamente criado para inicializadores compartilhados. A saída do exemplo a seguir é incerta, porque a ordem exata do carregamento e, portanto, da execução do construtor compartilhado não está definida:
Module Test
Sub Main()
A.F()
B.F()
End Sub
End Module
Class A
Shared Sub New()
Console.WriteLine("Init A")
End Sub
Public Shared Sub F()
Console.WriteLine("A.F")
End Sub
End Class
Class B
Shared Sub New()
Console.WriteLine("Init B")
End Sub
Public Shared Sub F()
Console.WriteLine("B.F")
End Sub
End Class
A saída pode ser uma das seguintes:
Init A
A.F
Init B
B.F
ou
Init B
Init A
A.F
B.F
Por outro lado, o exemplo a seguir produz resultados previsíveis. Observe que o construtor para a classe A nunca executa, mesmo que a Shared classe B derive dele:
Module Test
Sub Main()
B.G()
End Sub
End Module
Class A
Shared Sub New()
Console.WriteLine("Init A")
End Sub
End Class
Class B
Inherits A
Shared Sub New()
Console.WriteLine("Init B")
End Sub
Public Shared Sub G()
Console.WriteLine("B.G")
End Sub
End Class
A saída é:
Init B
B.G
Também é possível construir dependências circulares que permitem Shared que variáveis com inicializadores de variáveis sejam observadas em seu estado de valor padrão, como no exemplo a seguir:
Class A
Public Shared X As Integer = B.Y + 1
End Class
Class B
Public Shared Y As Integer = A.X + 1
Shared Sub Main()
Console.WriteLine("X = " & A.X & ", Y = " & B.Y)
End Sub
End Class
Isto produz a saída:
X = 1, Y = 2
Para executar o Main método, o sistema primeiro carrega a classe B. O Shared construtor de class B prossegue para calcular o valor inicial de , que recursivamente faz com que a classe A seja carregada Yporque o valor de A.X é referenciado. O Shared construtor de classe A , por sua vez, prossegue para calcular o valor inicial de X, e, ao fazer isso, busca o valor padrão de Y, que é zero.
A.X é, portanto, inicializado em 1. O processo de carregamento A então é concluído, retornando ao cálculo do valor inicial de Y, cujo resultado se torna 2.
Se o Main método tivesse sido localizado na classe A, o exemplo teria produzido a seguinte saída:
X = 2, Y = 1
Evite referências circulares em Shared inicializadores variáveis, pois geralmente é impossível determinar a ordem em que as classes que contêm tais referências são carregadas.
Eventos
Os eventos são usados para notificar o código de uma ocorrência específica. Uma declaração de evento consiste em um identificador, um tipo de delegado ou uma lista de parâmetros, e uma cláusula opcional Implements .
EventMemberDeclaration
: RegularEventMemberDeclaration
| CustomEventMemberDeclaration
;
RegularEventMemberDeclaration
: Attributes? EventModifiers* 'Event'
Identifier ParametersOrType ImplementsClause? StatementTerminator
;
InterfaceEventMemberDeclaration
: Attributes? InterfaceEventModifiers* 'Event'
Identifier ParametersOrType StatementTerminator
;
ParametersOrType
: ( OpenParenthesis ParameterList? CloseParenthesis )?
| 'As' NonArrayTypeName
;
EventModifiers
: AccessModifier
| 'Shadows'
| 'Shared'
;
InterfaceEventModifiers
: 'Shadows'
;
Se um tipo de delegado for especificado, o tipo de delegado pode não ter um tipo de retorno. Se uma lista de parâmetros for especificada, ela pode não conter Optional parâmetros ou ParamArray . O domínio de acessibilidade dos tipos de parâmetros e/ou tipo de delegado deve ser o mesmo ou um superconjunto do domínio de acessibilidade do próprio evento. Os eventos podem ser compartilhados especificando o Shared modificador.
Além do nome do membro adicionado ao espaço de declaração do tipo, uma declaração de evento declara implicitamente vários outros membros. Dado um evento chamado X, os seguintes membros são adicionados ao espaço de declaração:
Se a forma da declaração for uma declaração de método, uma classe delegada aninhada nomeada
XEventHandlerserá introduzida. A classe de delegado aninhado corresponde à declaração de método e tem a mesma acessibilidade que o evento. Os atributos na lista de parâmetros aplicam-se aos parâmetros da classe delegada.Uma
Privatevariável de instância digitada como delegado, chamadaXEvent.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 em tempo de compilação resultará e as declarações implícitas add_X e remove_X serão ignoradas para fins de vinculação de nome. Não é possível substituir ou sobrecarregar nenhum dos membros introduzidos, embora seja possível sombreá-los em tipos derivados. Por exemplo, a declaração de classe
Class Raiser
Public Event Constructed(i As Integer)
End Class
é equivalente à seguinte declaração
Class Raiser
Public Delegate Sub ConstructedEventHandler(i As Integer)
Protected ConstructedEvent As ConstructedEventHandler
Public Sub add_Constructed(d As ConstructedEventHandler)
ConstructedEvent = _
CType( _
[Delegate].Combine(ConstructedEvent, d), _
Raiser.ConstructedEventHandler)
End Sub
Public Sub remove_Constructed(d As ConstructedEventHandler)
ConstructedEvent = _
CType( _
[Delegate].Remove(ConstructedEvent, d), _
Raiser.ConstructedEventHandler)
End Sub
End Class
Declarar um evento sem especificar um tipo de delegado é a sintaxe mais simples e compacta, mas tem a desvantagem de declarar um novo tipo de delegado para cada evento. Por exemplo, no exemplo a seguir, três tipos de delegados ocultos são criados, mesmo que todos os três eventos tenham a mesma lista de parâmetros:
Public Class Button
Public Event Click(sender As Object, e As EventArgs)
Public Event DoubleClick(sender As Object, e As EventArgs)
Public Event RightClick(sender As Object, e As EventArgs)
End Class
No exemplo a seguir, os eventos simplesmente usam o mesmo delegado, EventHandler:
Public Delegate Sub EventHandler(sender As Object, e As EventArgs)
Public Class Button
Public Event Click As EventHandler
Public Event DoubleClick As EventHandler
Public Event RightClick As EventHandler
End Class
Os eventos podem ser tratados de duas maneiras: estática ou dinamicamente. A manipulação estática de eventos é mais simples e requer apenas uma WithEvents variável e uma Handles cláusula. No exemplo a seguir, a classe Form1 manipula estaticamente o evento Click do objeto Button:
Public Class Form1
Public WithEvents Button1 As New Button()
Public Sub Button1_Click(sender As Object, e As EventArgs) _
Handles Button1.Click
Console.WriteLine("Button1 was clicked!")
End Sub
End Class
A manipulação dinâmica de eventos é mais complexa porque o evento deve ser explicitamente conectado e desconectado no código. A instrução AddHandler adiciona um manipulador para um evento e a instrução RemoveHandler remove um manipulador para um evento. O próximo exemplo mostra uma classe Form1 que adiciona Button1_Click como um manipulador de eventos para Button1o Click evento do :
Public Class Form1
Public Sub New()
' Add Button1_Click as an event handler for Button1's Click event.
AddHandler Button1.Click, AddressOf Button1_Click
End Sub
Private Button1 As Button = New Button()
Sub Button1_Click(sender As Object, e As EventArgs)
Console.WriteLine("Button1 was clicked!")
End Sub
Public Sub Disconnect()
RemoveHandler Button1.Click, AddressOf Button1_Click
End Sub
End Class
No método Disconnect, o manipulador de eventos é removido.
Eventos personalizados
Conforme discutido na seção anterior, as declarações de evento definem implicitamente um campo, um add_ método e um remove_ método que são usados para controlar os manipuladores de eventos. Em algumas situações, no entanto, pode ser desejável fornecer código personalizado para controlar manipuladores de eventos. Por exemplo, se uma classe define quarenta eventos dos quais apenas alguns serão manipulados, usar uma tabela de hash em vez de quarenta campos para rastrear os manipuladores para cada evento pode ser mais eficiente.
Os eventos personalizados permitem que os métodos e remove_X sejam definidos explicitamente, o que permite o add_X armazenamento personalizado para manipuladores de eventos.
Os eventos personalizados são declarados da mesma forma que os eventos que especificam um tipo de delegado são declarados, com a exceção de que a palavra-chave deve preceder a Event palavra-chaveCustom. Uma declaração de evento aduaneiro contém três declarações: uma AddHandler declaração, uma RemoveHandler declaração e uma RaiseEvent declaração. Nenhuma das declarações pode ter modificadores, embora possam ter atributos.
CustomEventMemberDeclaration
: Attributes? EventModifiers* 'Custom' 'Event'
Identifier 'As' TypeName ImplementsClause? StatementTerminator
EventAccessorDeclaration+
'End' 'Event' StatementTerminator
;
EventAccessorDeclaration
: AddHandlerDeclaration
| RemoveHandlerDeclaration
| RaiseEventDeclaration
;
AddHandlerDeclaration
: Attributes? 'AddHandler'
OpenParenthesis ParameterList CloseParenthesis LineTerminator
Block?
'End' 'AddHandler' StatementTerminator
;
RemoveHandlerDeclaration
: Attributes? 'RemoveHandler'
OpenParenthesis ParameterList CloseParenthesis LineTerminator
Block?
'End' 'RemoveHandler' StatementTerminator
;
RaiseEventDeclaration
: Attributes? 'RaiseEvent'
OpenParenthesis ParameterList CloseParenthesis LineTerminator
Block?
'End' 'RaiseEvent' StatementTerminator
;
Por exemplo:
Class Test
Private Handlers As EventHandler
Public Custom Event TestEvent As EventHandler
AddHandler(value As EventHandler)
Handlers = CType([Delegate].Combine(Handlers, value), _
EventHandler)
End AddHandler
RemoveHandler(value as EventHandler)
Handlers = CType([Delegate].Remove(Handlers, value), _
EventHandler)
End RemoveHandler
RaiseEvent(sender As Object, e As EventArgs)
Dim TempHandlers As EventHandler = Handlers
If TempHandlers IsNot Nothing Then
TempHandlers(sender, e)
End If
End RaiseEvent
End Event
End Class
A AddHandler declaração e RemoveHandler usa um ByVal parâmetro, que deve ser do tipo delegado do evento. Quando uma AddHandler instrução ou RemoveHandler é executada (ou uma Handles cláusula lida automaticamente com um evento), a declaração correspondente será chamada. A RaiseEvent declaração usa os mesmos parâmetros que o delegado de evento e será chamada quando uma RaiseEvent instrução for executada. Todas as declarações devem ser fornecidas e são consideradas subrotinas.
Observe que AddHandler, RemoveHandler e RaiseEvent as declarações têm a mesma restrição de posicionamento de linha que as sub-rotinas. A instrução inicial, a instrução final e o bloco devem aparecer no início de uma linha lógica.
Além do nome do membro adicionado ao espaço de declaração do tipo, uma declaração de evento personalizada declara implicitamente vários outros membros. Dado um evento chamado X, os seguintes membros são adicionados ao espaço de declaração:
Um método chamado
add_X, correspondente à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 em tempo de compilação resultará e as declarações implícitas serão todas ignoradas para fins de vinculação de nome. Não é possível substituir ou sobrecarregar nenhum dos membros introduzidos, embora seja possível sombreá-los em tipos derivados.
Nota.
Custom não é uma palavra reservada.
Eventos personalizados em assemblies do WinRT
A partir do Microsoft Visual Basic 11.0, os eventos declarados em um arquivo compilado com /target:winmdobj, ou declarados em uma interface em tal arquivo e, em seguida, implementados em outro lugar, são tratados de forma um pouco diferente.
As ferramentas externas usadas para construir o winmd normalmente permitirão apenas certos tipos de delegados, como
System.EventHandler(Of T)ouSystem.TypedEventHandle(Of T, U), e não permitirão outros.O
XEventcampo tem o tipoSystem.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenTable(Of T)ondeTé o tipo delegado.O acessador AddHandler retorna um
System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken, e o acessador RemoveHandler usa um único parâmetro do mesmo tipo.
Aqui está um exemplo de tal evento personalizado.
Imports System.Runtime.InteropServices.WindowsRuntime
Public NotInheritable Class ClassInWinMD
Private XEvent As EventRegistrationTokenTable(Of EventHandler(Of Integer))
Public Custom Event X As EventHandler(Of Integer)
AddHandler(handler As EventHandler(Of Integer))
Return EventRegistrationTokenTable(Of EventHandler(Of Integer)).
GetOrCreateEventRegistrationTokenTable(XEvent).
AddEventHandler(handler)
End AddHandler
RemoveHandler(token As EventRegistrationToken)
EventRegistrationTokenTable(Of EventHandler(Of Integer)).
GetOrCreateEventRegistrationTokenTable(XEvent).
RemoveEventHandler(token)
End RemoveHandler
RaiseEvent(sender As Object, i As Integer)
Dim table = EventRegistrationTokenTable(Of EventHandler(Of Integer)).
GetOrCreateEventRegistrationTokenTable(XEvent).
InvocationList
If table IsNot Nothing Then table(sender, i)
End RaiseEvent
End Event
End Class
Constantes
Uma constante é um valor constante que é um membro de um tipo.
ConstantMemberDeclaration
: Attributes? ConstantModifier* 'Const' ConstantDeclarators StatementTerminator
;
ConstantModifier
: AccessModifier
| 'Shadows'
;
ConstantDeclarators
: ConstantDeclarator ( Comma ConstantDeclarator )*
;
ConstantDeclarator
: Identifier ( 'As' TypeName )? Equals ConstantExpression StatementTerminator
;
As constantes são implicitamente compartilhadas. Se a declaração contiver uma As cláusula, a cláusula especifica o tipo de membro introduzido pela declaração. Se o tipo é omitido, então o tipo da constante é inferido. O tipo de constante pode ser apenas um tipo primitivo ou Object. Se uma constante é digitada como Object e não há nenhum caractere de tipo, o tipo real da constante será o tipo da expressão constante. Caso contrário, o tipo da constante é o tipo do caractere de tipo da constante.
O exemplo a seguir mostra uma classe chamada Constants que tem duas constantes públicas:
Class Constants
Public Const A As Integer = 1
Public Const B As Integer = A + 1
End Class
As constantes podem ser acessadas através da classe, como no exemplo a seguir, que imprime os valores de Constants.A e Constants.B.
Module Test
Sub Main()
Console.WriteLine(Constants.A & ", " & Constants.B)
End Sub
End Module
Uma declaração constante que declara várias constantes é equivalente a várias declarações de constantes únicas. O exemplo a seguir declara três constantes em uma instrução de declaração.
Class A
Protected Const x As Integer = 1, y As Long = 2, z As Short = 3
End Class
Esta declaração é equivalente ao seguinte:
Class A
Protected Const x As Integer = 1
Protected Const y As Long = 2
Protected Const z As Short = 3
End Class
O domínio de acessibilidade do tipo da constante deve ser o mesmo ou um superconjunto do domínio de acessibilidade da própria constante. A expressão constante deve produzir um valor do tipo da constante ou de um tipo que seja implicitamente conversível para o tipo da constante. A expressão constante pode não ser circular; ou seja, uma constante pode não ser definida em termos de si mesma.
O compilador avalia automaticamente as declarações constantes na ordem apropriada. No exemplo a seguir, o compilador primeiro avalia Y, depois Ze, finalmente X, produzindo os valores 10, 11 e 12, respectivamente.
Class A
Public Const X As Integer = B.Z + 1
Public Const Y As Integer = 10
End Class
Class B
Public Const Z As Integer = A.Y + 1
End Class
Quando um nome simbólico para um valor constante é desejado, mas o tipo do valor não é permitido em uma declaração constante ou quando o valor não pode ser calculado em tempo de compilação por uma expressão constante, uma variável somente leitura pode ser usada em vez disso.
Instância e variáveis compartilhadas
Uma instância ou variável compartilhada é um membro de um tipo que pode armazenar informações.
VariableMemberDeclaration
: Attributes? VariableModifier+ VariableDeclarators StatementTerminator
;
VariableModifier
: AccessModifier
| 'Shadows'
| 'Shared'
| 'ReadOnly'
| 'WithEvents'
| 'Dim'
;
VariableDeclarators
: VariableDeclarator ( Comma VariableDeclarator )*
;
VariableDeclarator
: VariableIdentifiers 'As' ObjectCreationExpression
| VariableIdentifiers ( 'As' TypeName )? ( Equals Expression )?
;
VariableIdentifiers
: VariableIdentifier ( Comma VariableIdentifier )*
;
VariableIdentifier
: Identifier IdentifierModifiers
;
O Dim modificador deve ser especificado se nenhum modificador for especificado, mas pode ser omitido de outra forma. Uma única declaração de variável pode incluir vários declaradores de variáveis; Cada declarador de variável introduz uma nova instância ou membro compartilhado.
Se um inicializador for especificado, apenas uma instância ou variável compartilhada poderá ser declarada pelo declarador de variável:
Class Test
Dim a, b, c, d As Integer = 10 ' Invalid: multiple initialization
End Class
Esta restrição não se aplica aos inicializadores de objeto:
Class Test
Dim a, b, c, d As New Collection() ' OK
End Class
Uma variável declarada com o Shared modificador é uma variável compartilhada. Uma variável compartilhada identifica exatamente um local de armazenamento, independentemente do número de instâncias do tipo que são criadas. Uma variável compartilhada passa a existir quando um programa começa a ser executado e deixa de existir quando o programa é encerrado.
Uma variável compartilhada é compartilhada apenas entre instâncias de um determinado tipo genérico fechado. Por exemplo, o programa:
Class C(Of V)
Shared InstanceCount As Integer = 0
Public Sub New()
InstanceCount += 1
End Sub
Public Shared ReadOnly Property Count() As Integer
Get
Return InstanceCount
End Get
End Property
End Class
Class Application
Shared Sub Main()
Dim x1 As New C(Of Integer)()
Console.WriteLine(C(Of Integer).Count)
Dim x2 As New C(Of Double)()
Console.WriteLine(C(Of Integer).Count)
Dim x3 As New C(Of Integer)()
Console.WriteLine(C(Of Integer).Count)
End Sub
End Class
Imprime:
1
1
2
Uma variável declarada sem o Shared modificador é chamada de variável de instância. Cada instância de uma classe contém uma cópia separada de todas as variáveis de instância da classe. Uma variável de instância de um tipo de referência surge quando uma nova instância desse tipo é criada e deixa de existir quando não há referências a essa instância e o Finalize método foi executado. Uma variável de instância de um tipo de valor tem exatamente o mesmo tempo de vida que a variável à qual pertence. Em outras palavras, quando uma variável de um tipo de valor passa a existir ou deixa de existir, o mesmo acontece com a variável de instância do tipo de valor.
Se o declarador contiver uma As cláusula, esta especifica o tipo de membros introduzidos pela declaração. Se o tipo for omitido e a semântica estrita estiver sendo usada, ocorrerá um erro em tempo de compilação. Caso contrário, o tipo dos membros é implicitamente Object ou o tipo do caractere de tipo dos membros.
Nota. Não há ambiguidade na sintaxe: se um declarador omitir um tipo, ele sempre usará o tipo de um declarador a seguir.
O domínio de acessibilidade de uma instância ou do tipo de elemento de matriz de uma instância ou variável compartilhada deve ser o mesmo ou um superconjunto do domínio de acessibilidade da instância ou da própria variável compartilhada.
O exemplo a seguir mostra uma Color classe que tem variáveis de instância interna chamadas redPart, greenParte bluePart:
Class Color
Friend redPart As Short
Friend bluePart As Short
Friend greenPart As Short
Public Sub New(red As Short, blue As Short, green As Short)
redPart = red
bluePart = blue
greenPart = green
End Sub
End Class
Read-Only Variáveis
Quando uma instância ou declaração de variável compartilhada inclui um ReadOnly modificador, as atribuições às variáveis introduzidas pela declaração podem ocorrer apenas como parte da declaração ou em um construtor na mesma classe. Especificamente, as atribuições a uma instância somente leitura ou variável compartilhada são permitidas somente nas seguintes situações:
Na declaração de variável que introduz a instância ou variável compartilhada (incluindo um inicializador de variável na declaração).
Para uma variável de instância, nos construtores de instância da classe que contém a declaração de variável. A variável de instância só pode ser acessada de forma não qualificada ou através de
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 calculado em tempo de compilação por uma expressão constante.
Segue-se um exemplo da primeira aplicação deste tipo, em que as variáveis de cor partilhadas são declaradas ReadOnly para evitar que sejam alteradas por outros programas:
Class Color
Friend redPart As Short
Friend bluePart As Short
Friend greenPart As Short
Public Sub New(red As Short, blue As Short, green As Short)
redPart = red
bluePart = blue
greenPart = green
End Sub
Public Shared ReadOnly Red As Color = New Color(&HFF, 0, 0)
Public Shared ReadOnly Blue As Color = New Color(0, &HFF, 0)
Public Shared ReadOnly Green As Color = New Color(0, 0, &HFF)
Public Shared ReadOnly White As Color = New Color(&HFF, &HFF, &HFF)
End Class
Constantes e variáveis compartilhadas somente leitura têm semânticas diferentes. Quando uma expressão faz referência a uma constante, o valor da constante é obtido em tempo de compilação, mas quando uma expressão faz referência a uma variável compartilhada somente leitura, o valor da variável compartilhada não é obtido até o tempo de execução. Considere a seguinte aplicação, que consiste em dois programas separados.
file1.vb:
Namespace Program1
Public Class Utils
Public Shared ReadOnly X As Integer = 1
End Class
End Namespace
file2.vb:
Namespace Program2
Module Test
Sub Main()
Console.WriteLine(Program1.Utils.X)
End Sub
End Module
End Namespace
Os namespaces Program1 e Program2 denotam dois programas que são compilados separadamente. Como a variável Program1.Utils.X é declarada como Shared ReadOnly, a saída do Console.WriteLine valor pela instrução não é conhecida em tempo de compilação, mas é obtida em tempo de execução. Assim, se o valor de X for alterado e Program1 for recompilado, a Console.WriteLine instrução produzirá o novo valor, mesmo que Program2 não seja recompilado. No entanto, se X tivesse sido uma constante, o valor de teria sido obtido no momento Program2 em que foi compilado, e teria permanecido X inalterado por alterações até Program1Program2 ser recompilado.
Variáveis WithEvents
Um tipo pode declarar que lida com algum conjunto de eventos gerados por uma de suas variáveis de instância ou compartilhadas declarando a instância ou variável compartilhada que gera os eventos com o WithEvents modificador. Por exemplo:
Class Raiser
Public Event E1()
Public Sub Raise()
RaiseEvent E1
End Sub
End Class
Module Test
Private WithEvents x As Raiser
Private Sub E1Handler() Handles x.E1
Console.WriteLine("Raised")
End Sub
Public Sub Main()
x = New Raiser()
End Sub
End Module
Neste exemplo, o método E1Handler manipula o evento E1 que é gerado pela instância do tipo Raiser armazenado na variável xde instância .
O WithEvents modificador faz com que a variável seja renomeada com um sublinhado à esquerda e substituída por uma propriedade do mesmo nome que faz a conexão do evento. Por exemplo, se o nome da variável for F, ela será renomeada para _F e uma propriedade F será implicitamente declarada. Se houver uma colisão entre o novo nome da variável e outra declaração, um erro em tempo de compilação será relatado. Todos os atributos aplicados à variável são transferidos para a variável renomeada.
A propriedade implícita criada por uma WithEvents declaração cuida de conectar e desconectar os manipuladores de eventos relevantes. Quando um valor é atribuído à variável, a propriedade primeiro chama o remove método para o evento na instância atualmente na variável (desconectando o manipulador de eventos existente, se houver). Em seguida, a atribuição é feita e a propriedade chama o add método para o evento na nova instância na variável (conectando o novo manipulador de eventos). O código a seguir é equivalente ao código acima para o módulo Testpadrão:
Module Test
Private _x As Raiser
Public Property x() As Raiser
Get
Return _x
End Get
Set (Value As Raiser)
' Unhook any existing handlers.
If _x IsNot Nothing Then
RemoveHandler _x.E1, AddressOf E1Handler
End If
' Change value.
_x = Value
' Hook-up new handlers.
If _x IsNot Nothing Then
AddHandler _x.E1, AddressOf E1Handler
End If
End Set
End Property
Sub E1Handler()
Console.WriteLine("Raised")
End Sub
Sub Main()
x = New Raiser()
End Sub
End Module
Não é válido declarar uma instância ou variável compartilhada como WithEvents se a variável fosse digitada como uma estrutura. Além disso, WithEvents não pode ser especificado em uma estrutura e WithEventsReadOnly não pode ser combinado.
Inicializadores variáveis
As declarações de variáveis compartilhadas e de instância em classes e declarações de variáveis de instância (mas não declarações de variáveis compartilhadas) em estruturas podem incluir inicializadores de variáveis. Para Shared variáveis, inicializadores de variáveis correspondem a instruções de atribuição que são executadas após o início do programa, mas antes que a Shared variável seja referenciada pela primeira vez. Por exemplo, variáveis, inicializadores de variáveis correspondem a instruções de atribuição que são executadas quando uma instância da classe é criada. As estruturas não podem ter inicializadores de variáveis de instância porque seus construtores sem parâmetros não podem ser modificados.
Considere o seguinte exemplo:
Class Test
Public Shared x As Double = Math.Sqrt(2.0)
Public i As Integer = 100
Public s As String = "Hello"
End Class
Module TestModule
Sub Main()
Dim a As New Test()
Console.WriteLine("x = " & Test.x & ", i = " & a.i & ", s = " & a.s)
End Sub
End Module
O exemplo produz a seguinte saída:
x = 1.4142135623731, i = 100, s = Hello
Uma atribuição a x ocorre quando a classe é carregada e as atribuições a i e s ocorrem quando uma nova instância da classe é criada.
É útil pensar em inicializadores de variáveis como instruções de atribuição que são inseridas automaticamente no bloco do construtor do tipo. O exemplo a seguir contém vários inicializadores de variável de instância.
Class A
Private x As Integer = 1
Private y As Integer = -1
Private count As Integer
Public Sub New()
count = 0
End Sub
Public Sub New(n As Integer)
count = n
End Sub
End Class
Class B
Inherits A
Private sqrt2 As Double = Math.Sqrt(2.0)
Private items As ArrayList = New ArrayList(100)
Private max As Integer
Public Sub New()
Me.New(100)
items.Add("default")
End Sub
Public Sub New(n As Integer)
MyBase.New(n - 1)
max = n
End Sub
End Class
O exemplo corresponde ao código mostrado abaixo, onde cada comentário indica uma instrução inserida automaticamente.
Class A
Private x, y, count As Integer
Public Sub New()
MyBase.New ' Invoke object() constructor.
x = 1 ' This is a variable initializer.
y = -1 ' This is a variable initializer.
count = 0
End Sub
Public Sub New(n As Integer)
MyBase.New ' Invoke object() constructor.
x = 1 ' This is a variable initializer.
y = - 1 ' This is a variable initializer.
count = n
End Sub
End Class
Class B
Inherits A
Private sqrt2 As Double
Private items As ArrayList
Private max As Integer
Public Sub New()
Me.New(100)
items.Add("default")
End Sub
Public Sub New(n As Integer)
MyBase.New(n - 1)
sqrt2 = Math.Sqrt(2.0) ' This is a variable initializer.
items = New ArrayList(100) ' This is a variable initializer.
max = n
End Sub
End Class
Todas as variáveis são inicializadas com o valor padrão de seu tipo antes que qualquer inicializador de variável seja executado. Por exemplo:
Class Test
Public Shared b As Boolean
Public i As Integer
End Class
Module TestModule
Sub Main()
Dim t As New Test()
Console.WriteLine("b = " & Test.b & ", i = " & t.i)
End Sub
End Module
Como b é inicializado automaticamente para seu valor padrão quando a classe é carregada e i é automaticamente inicializado para seu valor padrão quando uma instância da classe é criada, o código anterior produz a seguinte saída:
b = False, i = 0
Cada inicializador de variável deve produzir um valor do tipo da variável ou de um tipo que seja implicitamente conversível para o tipo da variável. Um inicializador de variável pode ser circular ou referir-se a uma variável que será inicializada depois dele, caso em que o valor da variável referenciada é seu valor padrão para os fins do inicializador. Tal inicializador é de valor duvidoso.
Existem três formas de inicializadores variáveis: inicializadores regulares, inicializadores de tamanho de matriz e inicializadores de objeto. Os dois primeiros formulários aparecem após um sinal de igual que segue o nome do tipo, os dois últimos fazem parte da própria declaração. Apenas uma forma de inicializador pode ser usada em qualquer declaração específica.
Inicializadores regulares
Um inicializador regular é uma expressão que é implicitamente conversível para o tipo da variável. Ele aparece após um sinal de igual que segue o nome do tipo e deve ser classificado como um valor. Por exemplo:
Module Test
Dim x As Integer = 10
Dim y As Integer = 20
Sub Main()
Console.WriteLine("x = " & x & ", y = " & y)
End Sub
End Module
Este programa produz a saída:
x = 10, y = 20
Se uma declaração de variável tiver um inicializador regular, apenas uma única variável poderá ser declarada de cada vez. Por exemplo:
Module Test
Sub Main()
' OK, only one variable declared at a time.
Dim x As Integer = 10, y As Integer = 20
' Error: Can't initialize multiple variables at once.
Dim a, b As Integer = 10
End Sub
End Module
Inicializadores de objeto
Um inicializador de objeto é especificado usando uma expressão de criação de objeto no lugar do nome do tipo. Um inicializador de objeto é equivalente a um inicializador regular que atribui o resultado da expressão de criação de objeto à variável. Então
Module TestModule
Sub Main()
Dim x As New Test(10)
End Sub
End Module
é equivalente a
Module TestModule
Sub Main()
Dim x As Test = New Test(10)
End Sub
End Module
O parêntese em um inicializador de objeto é sempre interpretado como a lista de argumentos para o construtor e nunca como modificadores de tipo de matriz. Um nome de variável com um inicializador de objeto não pode ter um modificador de tipo de matriz ou um modificador de tipo anulável.
Array-Size inicializadores
Um inicializador de tamanho de matriz é um modificador no nome da variável que fornece um conjunto de limites superiores de dimensão indicados por expressões.
ArraySizeInitializationModifier
: OpenParenthesis BoundList CloseParenthesis ArrayTypeModifiers?
;
BoundList
: Bound ( Comma Bound )*
;
Bound
: Expression
| '0' 'To' Expression
;
As expressões de limite superior devem ser classificadas como valores e devem ser implicitamente convertíveis em Integer. O conjunto de limites superiores é equivalente a um inicializador variável de uma expressão de criação de matriz com os limites superiores fornecidos. O número de dimensões do tipo de matriz é inferido a partir do inicializador de tamanho de matriz. Então
Module Test
Sub Main()
Dim x(5, 10) As Integer
End Sub
End Module
é equivalente a
Module Test
Sub Main()
Dim x As Integer(,) = New Integer(5, 10) {}
End Sub
End Module
Todos os limites superiores devem ser iguais ou maiores que -1, e todas as dimensões devem ter um limite superior especificado. Se o tipo de elemento da matriz que está sendo inicializada for um tipo de matriz, os modificadores de tipo de matriz irão para a direita do inicializador de tamanho de matriz. Por exemplo
Module Test
Sub Main()
Dim x(5,10)(,,) As Integer
End Sub
End Module
Declara uma variável x local cujo tipo é uma matriz bidimensional de matrizes tridimensionais de Integer, inicializada para uma matriz com limites de 0..5 na primeira dimensão e 0..10 na segunda dimensão. Não é possível usar um inicializador de tamanho de matriz para inicializar os elementos de uma variável cujo tipo é uma matriz de matrizes.
Uma declaração de variável com um inicializador de tamanho de matriz não pode ter um modificador de tipo de matriz em seu tipo ou um inicializador regular.
System.MarshalByRefObject Classes
As classes que derivam da classe System.MarshalByRefObject são empacotadas através dos limites de contexto usando proxies (ou seja, por referência) em vez de cópia (ou seja, por valor). Isso significa que uma instância de tal classe pode não ser uma instância verdadeira, mas pode ser apenas um stub que controla acessos variáveis e chamadas de método através de um limite de contexto.
Como resultado, não é possível criar uma referência ao local de armazenamento de variáveis definidas em tais classes. Isso significa que as variáveis digitadas como classes derivadas não podem ser passadas para parâmetros de referência, e métodos e variáveis de variáveis digitadas como tipos de System.MarshalByRefObject valor não podem ser acessados. Em vez disso, o Visual Basic trata variáveis definidas em tais classes como se fossem propriedades (já que as restrições são as mesmas em propriedades).
Há uma exceção a esta regra: um membro implícita ou explicitamente qualificado com Me está isento das restrições acima, porque Me é sempre garantido ser um objeto real, não um proxy.
Propriedades
As propriedades são uma extensão natural das variáveis; ambos são membros nomeados com tipos associados, e a sintaxe para acessar variáveis e propriedades é a mesma. Ao contrário das variáveis, no entanto, as propriedades não denotam locais de armazenamento. Em vez disso, as propriedades têm acessadores, que especificam as instruções a serem executadas para ler ou gravar seus valores.
As propriedades são definidas com declarações de propriedade. A primeira parte de uma declaração de propriedade é semelhante a uma declaração de campo. A segunda parte inclui um Get acessor e/ou um Set acessador.
PropertyMemberDeclaration
: RegularPropertyMemberDeclaration
| MustOverridePropertyMemberDeclaration
| AutoPropertyMemberDeclaration
;
PropertySignature
: 'Property'
Identifier ( OpenParenthesis ParameterList? CloseParenthesis )?
( 'As' Attributes? TypeName )?
;
RegularPropertyMemberDeclaration
: Attributes? PropertyModifier* PropertySignature
ImplementsClause? LineTerminator
PropertyAccessorDeclaration+
'End' 'Property' StatementTerminator
;
MustOverridePropertyMemberDeclaration
: Attributes? MustOverridePropertyModifier+ PropertySignature
ImplementsClause? StatementTerminator
;
AutoPropertyMemberDeclaration
: Attributes? AutoPropertyModifier* 'Property' Identifier
( OpenParenthesis ParameterList? CloseParenthesis )?
( 'As' Attributes? TypeName )? ( Equals Expression )?
ImplementsClause? LineTerminator
| Attributes? AutoPropertyModifier* 'Property' Identifier
( OpenParenthesis ParameterList? CloseParenthesis )?
'As' Attributes? 'New'
( NonArrayTypeName ( OpenParenthesis ArgumentList? CloseParenthesis )? )?
ObjectCreationExpressionInitializer?
ImplementsClause? LineTerminator
;
InterfacePropertyMemberDeclaration
: Attributes? InterfacePropertyModifier* PropertySignature StatementTerminator
;
AutoPropertyModifier
: AccessModifier
| 'Shadows'
| 'Shared'
| 'Overridable'
| 'NotOverridable'
| 'Overrides'
| 'Overloads'
;
PropertyModifier
: AutoPropertyModifier
| 'Default'
| 'ReadOnly'
| 'WriteOnly'
| 'Iterator'
;
MustOverridePropertyModifier
: PropertyModifier
| 'MustOverride'
;
InterfacePropertyModifier
: 'Shadows'
| 'Overloads'
| 'Default'
| 'ReadOnly'
| 'WriteOnly'
;
PropertyAccessorDeclaration
: PropertyGetDeclaration
| PropertySetDeclaration
;
No exemplo abaixo, a Button classe define uma Caption propriedade.
Public Class Button
Private captionValue As String
Public Property Caption() As String
Get
Return captionValue
End Get
Set (Value As String)
captionValue = value
Repaint()
End Set
End Property
...
End Class
Com base na Button classe acima, o seguinte é um exemplo de uso da Caption propriedade:
Dim okButton As Button = New Button()
okButton.Caption = "OK" ' Invokes Set accessor.
Dim s As String = okButton.Caption ' Invokes Get accessor.
Aqui, o Set acessador é invocado atribuindo um valor à propriedade e o Get acessador é invocado fazendo referência à propriedade em uma expressão.
Se nenhum tipo for especificado para uma propriedade e semântica estrita estiver sendo usada, ocorrerá um erro em tempo de compilação; caso contrário, o tipo da propriedade é implicitamente Object ou o tipo do caractere de tipo da propriedade. Uma declaração de propriedade pode conter um Get acessador, que recupera o valor da propriedade, um Set acessador, que armazena o valor da propriedade, ou ambos. Como uma propriedade declara implicitamente métodos, uma propriedade pode ser declarada com os mesmos modificadores que um método. Se a propriedade é definida em uma interface ou definida com o MustOverride modificador, o corpo da propriedade e a End construção devem ser omitidos, caso contrário, ocorre um erro em tempo de compilação.
A lista de parâmetros de índice compõe a assinatura da propriedade, portanto, as propriedades podem ser sobrecarregadas nos parâmetros de índice, mas não no tipo da propriedade. A lista de parâmetros de índice é a mesma que para um método regular. No entanto, nenhum dos parâmetros pode ser modificado com o ByRef modificador e nenhum deles pode ser nomeado Value (que é reservado para o parâmetro de valor implícito no Set acessador).
Um imóvel pode ser declarado da seguinte forma:
Se a propriedade não especificar nenhum modificador de tipo de propriedade, a propriedade deverá ter um
Getacessador e umSetacessador. Diz-se que a propriedade é uma propriedade de leitura-gravação.Se a propriedade especificar o
ReadOnlymodificador, a propriedade deve ter umGetacessador e pode não ter umSetacessador. Diz-se que a propriedade é somente leitura. É um erro durante a compilação que uma propriedade só de leitura seja o destino de uma atribuição.Se a propriedade especificar o
WriteOnlymodificador, a propriedade deve ter umSetacessador e pode não ter umGetacessador. Diz-se que a propriedade é propriedade somente escrita. É um erro em tempo de compilação fazer referência a uma propriedade somente gravação em uma expressão, exceto como o destino de uma atribuição ou como um argumento para um método.
Os Get e Set os acessadores de um imóvel não são membros distintos, e não é possível declarar os acessadores de um imóvel separadamente. O exemplo a seguir não declara uma única propriedade de leitura-gravação. Em vez disso, ele declara duas propriedades com o mesmo nome, uma somente leitura e uma somente gravação:
Class A
Private nameValue As String
' Error, contains a duplicate member name.
Public ReadOnly Property Name() As String
Get
Return nameValue
End Get
End Property
' Error, contains a duplicate member name.
Public WriteOnly Property Name() As String
Set (Value As String)
nameValue = value
End Set
End Property
End Class
Como dois membros declarados na mesma classe não podem ter o mesmo nome, o exemplo causa um erro em tempo de compilação.
Por padrão, a acessibilidade de um imóvel Get e Set acessadores é a mesma que a acessibilidade do próprio imóvel. No entanto, os acessadores também Set podem especificar a Get acessibilidade separadamente da propriedade. Nesse caso, a acessibilidade de um acessador deve ser mais restritiva do que a acessibilidade da propriedade e apenas um acessor pode ter um nível de acessibilidade diferente do imóvel. Os tipos de acesso são considerados mais ou menos restritivos da seguinte forma:
Privateé mais restritiva do quePublic,Protected Friend,Protected, ouFriend.Friendé mais restritiva do queProtected FriendouPublic.Protectedé mais restritiva do queProtected FriendouPublic.Protected Friendé mais restritiva 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 faz sombra a uma propriedade, a propriedade derivada oculta a propriedade sombreada em relação à leitura e à escrita. No exemplo a seguir, a P propriedade in B oculta a P propriedade em A relação à leitura e à escrita:
Class A
Public WriteOnly Property P() As Integer
Set (Value As Integer)
End Set
End Property
End Class
Class B
Inherits A
Public Shadows ReadOnly Property P() As Integer
Get
End Get
End Property
End Class
Module Test
Sub Main()
Dim x As B = New B
B.P = 10 ' Error, B.P is read-only.
End Sub
End Module
O domínio de acessibilidade do tipo de retorno ou tipos de parâmetro deve ser o mesmo ou um superconjunto do domínio de acessibilidade da própria propriedade. Uma propriedade só pode ter um Set acessador e um Get acessador.
Exceto para diferenças na sintaxe de declaração e invocação, Overridable, , NotOverridableOverrides, MustOverride, e MustInherit propriedades se comportam exatamente como Overridable, NotOverridable, Overrides, MustOverride, e MustInherit métodos. Quando uma propriedade é substituída, a propriedade de substituição deve ser do mesmo tipo (leitura-gravação, somente leitura, somente gravação). Uma Overridable propriedade não pode conter um Private acessador.
No exemplo X a seguir é uma Overridable propriedade somente leitura, Y é uma propriedade de Overridable leitura-gravação e Z é uma propriedade de MustOverride leitura-gravação.
MustInherit Class A
Private _y As Integer
Public Overridable ReadOnly Property X() As Integer
Get
Return 0
End Get
End Property
Public Overridable Property Y() As Integer
Get
Return _y
End Get
Set (Value As Integer)
_y = value
End Set
End Property
Public MustOverride Property Z() As Integer
End Class
Porque Z é MustOverride, a classe A que contém deve ser declarada MustInherit.
Por outro lado, uma classe que deriva da classe A é mostrada abaixo:
Class B
Inherits A
Private _z As Integer
Public Overrides ReadOnly Property X() As Integer
Get
Return MyBase.X + 1
End Get
End Property
Public Overrides Property Y() As Integer
Get
Return MyBase.Y
End Get
Set (Value As Integer)
If value < 0 Then
MyBase.Y = 0
Else
MyBase.Y = Value
End If
End Set
End Property
Public Overrides Property Z() As Integer
Get
Return _z
End Get
Set (Value As Integer)
_z = Value
End Set
End Property
End Class
Aqui, as declarações de propriedades XeYZ substituem as propriedades base. Cada declaração de propriedade corresponde exatamente aos modificadores de acessibilidade, tipo e nome da propriedade herdada correspondente. O Get acessador de propriedade X e o Set acessador de propriedade Y usam a MyBase palavra-chave para acessar as propriedades herdadas. A declaração de propriedade Z sobrepõe-se à MustOverride propriedade - assim, não há membros pendentes MustOverride na classe B, e B é permitido ser uma classe regular.
As propriedades podem ser usadas para atrasar a inicialização de um recurso até o momento em que ele é referenciado pela primeira vez. Por exemplo:
Imports System.IO
Public Class ConsoleStreams
Private Shared reader As TextReader
Private Shared writer As TextWriter
Private Shared errors As TextWriter
Public Shared ReadOnly Property [In]() As TextReader
Get
If reader Is Nothing Then
reader = Console.In
End If
Return reader
End Get
End Property
Public Shared ReadOnly Property Out() As TextWriter
Get
If writer Is Nothing Then
writer = Console.Out
End If
Return writer
End Get
End Property
Public Shared ReadOnly Property [Error]() As TextWriter
Get
If errors Is Nothing Then
errors = Console.Error
End If
Return errors
End Get
End Property
End Class
A ConsoleStreams classe contém três propriedades, In, Out, e Error, que representam os dispositivos padrão de entrada, saída e erro, respectivamente. Ao expor esses membros como propriedades, a classe ConsoleStreams pode atrasar a sua inicialização até que eles sejam realmente usados. Por exemplo, ao fazer a primeira referência à Out propriedade, como em ConsoleStreams.Out.WriteLine("hello, world"), o subjacente TextWriter para o dispositivo de saída é inicializado. Mas se o aplicativo não fizer referência às In propriedades e Error , nenhum objeto será criado para esses dispositivos.
Obter declarações de acesso
Um Get acessador (getter) é declarado usando uma declaração de propriedade Get . Uma declaração de propriedade Get consiste na palavra-chave Get seguida por um bloco de instrução. Dada uma propriedade chamada P, uma Get declaração de acessador declara implicitamente um método com o nome get_P com os mesmos modificadores, tipo e lista de parâmetros que a propriedade. Se o tipo contiver uma declaração com esse nome, um erro em tempo de compilação resultará, mas a declaração implícita será ignorada para fins de vinculação de nome.
Uma variável local especial, que é implicitamente declarada no espaço de declaração do Get corpo do acessador com o mesmo nome da propriedade, representa o valor de retorno da propriedade. A variável local tem semântica de resolução de nome especial quando usada em expressões. Se a variável local for usada em um contexto que espera uma expressão classificada como um grupo de métodos, como uma expressão de invocação, o nome será resolvido para a função em vez de para a variável local. Por exemplo:
ReadOnly Property F(i As Integer) As Integer
Get
If i = 0 Then
F = 1 ' Sets the return value.
Else
F = F(i - 1) ' Recursive call.
End If
End Get
End Property
O uso de parênteses pode causar situações ambíguas (como F(1) onde F está uma propriedade cujo tipo é uma matriz unidimensional). Em todas as situações ambíguas, o nome é resolvido para a propriedade em vez da variável local. Por exemplo:
ReadOnly Property F(i As Integer) As Integer()
Get
If i = 0 Then
F = new Integer(2) { 1, 2, 3 }
Else
F = F(i - 1) ' Recursive call, not index.
End If
End Get
End Property
Quando o fluxo de controle deixa o corpo do Get acessador, o valor da variável local é passado de volta para a expressão de invocação. Como invocar um Get acessador é conceitualmente equivalente a ler o valor de uma variável, é considerado um estilo de programação ruim para Get os acessadores ter efeitos colaterais observáveis, como ilustrado no exemplo a seguir:
Class Counter
Private Value As Integer
Public ReadOnly Property NextValue() As Integer
Get
Value += 1
Return Value
End Get
End Property
End Class
O valor da NextValue propriedade depende do número de vezes que a propriedade foi acessada anteriormente. Assim, acessar a propriedade produz um efeito colateral observável, e a propriedade deve, em vez disso, ser implementada como um método.
A convenção "sem efeitos colaterais" para Get acessadores não significa que Get os acessadores devem sempre ser escritos para simplesmente retornar valores armazenados em variáveis. De fato, Get os acessadores geralmente calculam o valor de uma propriedade acessando várias variáveis ou invocando métodos. No entanto, um acessador projetado Get corretamente não executa nenhuma ação que cause alterações observáveis no estado do objeto.
Nota.
Get Os acessadores têm a mesma restrição de posicionamento de linha que as sub-rotinas. A instrução inicial, a instrução final e o bloco devem aparecer no início de uma linha lógica.
PropertyGetDeclaration
: Attributes? AccessModifier? 'Get' LineTerminator
Block?
'End' 'Get' StatementTerminator
;
Definir declarações de acessador
Um Set acessador (setter) é declarado usando uma declaração de conjunto de propriedades. Uma declaração de conjunto de propriedades consiste na palavra-chave Set, uma lista de parâmetros opcional e um bloco de instrução. Dada uma propriedade chamada P, uma declaração setter declara implicitamente um método com o nome set_P com os mesmos modificadores e lista de parâmetros que a propriedade. Se o tipo contiver uma declaração com esse nome, um erro em tempo de compilação resultará, mas a declaração implícita será ignorada para fins de vinculação de nome.
Se uma lista de parâmetros for especificada, ela deve ter um membro, esse membro não deve ter modificadores exceto ByVal, e seu tipo deve ser o mesmo que o tipo da propriedade. O parâmetro representa o valor da propriedade que está sendo definida. Se o parâmetro for omitido, um parâmetro nomeado Value será declarado implicitamente.
Nota.
Set Os acessadores têm a mesma restrição de posicionamento de linha que as sub-rotinas. A instrução inicial, a instrução final e o bloco devem aparecer no início de uma linha lógica.
PropertySetDeclaration
: Attributes? AccessModifier? 'Set'
( OpenParenthesis ParameterList? CloseParenthesis )? LineTerminator
Block?
'End' 'Set' StatementTerminator
;
Propriedades padrão
Uma propriedade que especifica o modificador Default é chamada de propriedade padrão. Qualquer tipo que permita propriedades pode ter uma propriedade padrão, incluindo interfaces. A propriedade padrão pode ser referenciada sem ter que qualificar a instância com o nome da propriedade. Assim, dada uma aula
Class Test
Public Default ReadOnly Property Item(i As Integer) As Integer
Get
Return i
End Get
End Property
End Class
o código
Module TestModule
Sub Main()
Dim x As Test = New Test()
Dim y As Integer
y = x(10)
End Sub
End Module
é equivalente a
Module TestModule
Sub Main()
Dim x As Test = New Test()
Dim y As Integer
y = x.Item(10)
End Sub
End Module
Depois que uma propriedade é declarada Default, todas as propriedades sobrecarregadas nesse nome na hierarquia de herança se tornam a propriedade padrão, quer tenham sido declaradas Default ou não. Declarar uma propriedade Default em uma classe derivada quando a classe base declarou uma propriedade padrão por outro nome não requer nenhum outro modificador, como Shadows ou Overrides. Isso ocorre porque a propriedade padrão não tem identidade ou assinatura e, portanto, não pode ser sombreada ou sobrecarregada. Por exemplo:
Class Base
Public ReadOnly Default Property Item(i As Integer) As Integer
Get
Console.WriteLine("Base = " & i)
End Get
End Property
End Class
Class Derived
Inherits Base
' This hides Item, but does not change the default property.
Public Shadows ReadOnly Property Item(i As Integer) As Integer
Get
Console.WriteLine("Derived = " & i)
End Get
End Property
End Class
Class MoreDerived
Inherits Derived
' This declares a new default property, but not Item.
' This does not need to be declared Shadows
Public ReadOnly Default Property Value(i As Integer) As Integer
Get
Console.WriteLine("MoreDerived = " & i)
End Get
End Property
End Class
Module Test
Sub Main()
Dim x As MoreDerived = New MoreDerived()
Dim y As Integer
Dim z As Derived = x
y = x(10) ' Calls MoreDerived.Value.
y = x.Item(10) ' Calls Derived.Item
y = z(10) ' Calls Base.Item
End Sub
End Module
Este programa produzirá a saída:
MoreDerived = 10
Derived = 10
Base = 10
Todas as propriedades padrão declaradas dentro de um tipo devem ter o mesmo nome e, para maior clareza, devem especificar o Default modificador. Como uma propriedade padrão sem parâmetros de índice causaria uma situação ambígua ao atribuir instâncias da classe que contém, as propriedades padrão devem ter parâmetros de índice. Além disso, se uma propriedade sobrecarregada em um nome específico incluir o Default modificador, todas as propriedades sobrecarregadas nesse nome deverão especificá-lo. As propriedades padrão podem não ser Shared, e pelo menos um acessador da propriedade não deve ser Private.
Propriedades implementadas automaticamente
Se uma propriedade omitir a declaração de quaisquer acessadores, uma implementação da propriedade será fornecida automaticamente, a menos que a propriedade seja declarada em uma interface ou seja declarada MustOverride. Somente propriedades de leitura/gravação sem argumentos podem ser implementadas automaticamente; caso contrário, ocorrerá um erro em tempo de compilação.
Uma propriedade ximplementada automaticamente, mesmo que uma substitua outra propriedade, introduz uma variável _x local privada com o mesmo tipo da propriedade. Se houver uma colisão entre o nome da variável local e outra declaração, um erro em tempo de compilação será relatado. O acessador da Get propriedade implementada automaticamente retorna o valor do local e o acessador da propriedade Set que define o valor do local. Por exemplo, a declaração:
Public Property x() As Integer
é aproximadamente equivalente a:
Private _x As Integer
Public Property x() As Integer
Get
Return _x
End Get
Set (value As Integer)
_x = value
End Set
End Property
Assim como as declarações de variáveis, uma propriedade implementada automaticamente pode incluir um inicializador. Por exemplo:
Public Property x() As Integer = 10
Public Shared Property y() As New Customer() With { .Name = "Bob" }
Nota. Quando uma propriedade implementada automaticamente é inicializada, ela é inicializada através da propriedade, não do campo subjacente. Isso é para que as propriedades de substituição possam intercetar a inicialização, se necessário.
Os inicializadores de matriz são permitidos em propriedades implementadas automaticamente, exceto que não há como especificar os limites de matriz explicitamente. Por exemplo:
' Valid
Property x As Integer() = {1, 2, 3}
Property y As Integer(,) = {{1, 2, 3}, {12, 13, 14}, {11, 10, 9}}
' Invalid
Property x4(5) As Short
Propriedades do iterador
Uma propriedade iterator é uma propriedade com o Iterator modificador. É usado pela mesma razão que um método iterador (Section Iterator Methods) é usado -- como uma maneira conveniente de gerar uma sequência, que pode ser consumida For Each pela instrução. O Get acessador de uma propriedade iterator é interpretado da mesma maneira que um método iterator.
Uma propriedade iteradora deve ter um acessador explícito Get , e seu tipo deve ser IEnumerator, ou IEnumerable, ou IEnumerator(Of T)IEnumerable(Of T) para alguns T.
Aqui está um exemplo de uma propriedade iterator:
Class Family
Property Daughters As New List(Of String) From {"Beth", "Diane"}
Property Sons As New List(Of String) From {"Abe", "Carl"}
ReadOnly Iterator Property Children As IEnumerable(Of String)
Get
For Each name In Daughters : Yield name : Next
For Each name In Sons : Yield name : Next
End Get
End Property
End Class
Module Module1
Sub Main()
Dim x As New Family
For Each c In x.Children
Console.WriteLine(c) ' prints Beth, Diane, Abe, Carl
Next
End Sub
End Module
Operadores
Operadores são métodos que definem o significado de um operador existente do Visual Basic para a classe que contém. Quando o operador é aplicado à classe em uma expressão, o operador é compilado em uma chamada para o método de operador definido na classe. Definir um operador para uma classe também é conhecido como sobrecarregar o operador.
OperatorDeclaration
: Attributes? OperatorModifier* 'Operator' OverloadableOperator
OpenParenthesis ParameterList CloseParenthesis
( 'As' Attributes? TypeName )? LineTerminator
Block?
'End' 'Operator' StatementTerminator
;
OperatorModifier
: 'Public' | 'Shared' | 'Overloads' | 'Shadows' | 'Widening' | 'Narrowing'
;
OverloadableOperator
: '+' | '-' | '*' | '/' | '\\' | '&' | 'Like' | 'Mod' | 'And' | 'Or' | 'Xor'
| '^' | '<' '<' | '>' '>' | '=' | '<' '>' | '>' | '<' | '>' '=' | '<' '='
| 'Not' | 'IsTrue' | 'IsFalse' | 'CType'
;
Não é possível sobrecarregar um operador que já existe; Na prática, isto aplica-se principalmente aos operadores de conversão. Por exemplo, não é possível sobrecarregar a conversão de uma classe derivada para uma classe base:
Class Base
End Class
Class Derived
' Cannot redefine conversion from Derived to Base,
' conversion will be ignored.
Public Shared Widening Operator CType(s As Derived) As Base
...
End Operator
End Class
Os operadores também podem ser sobrecarregados no sentido comum da palavra:
Class Base
Public Shared Widening Operator CType(b As Base) As Integer
...
End Operator
Public Shared Narrowing Operator CType(i As Integer) As Base
...
End Operator
End Class
As declarações de operador não adicionam explicitamente nomes ao espaço de declaração do tipo que contém; no entanto, eles declaram implicitamente um método correspondente começando com os caracteres "op_". As seções a seguir listam os nomes de método correspondentes com cada operador.
Existem três classes de operadores que podem ser definidas: operadores unários, operadores binários e operadores de conversão. Todas as declarações de operador partilham certas restrições:
As declarações do operador devem ser
Publicsempre eShared. OPublicmodificador pode ser omitido em contextos onde o modificador será assumido.Os parâmetros de um operador não podem ser declarados
ByRef,OptionalouParamArray.O tipo de pelo menos um dos operandos ou o valor de retorno deve ser o tipo que contém o operador.
Não há nenhuma variável de retorno de função definida para operadores. Portanto, a
Returninstrução deve ser usada para retornar valores de um corpo operador.
A única exceção a essas restrições se aplica a tipos de valor anuláveis. Como os tipos de valor anulável não têm uma definição de tipo real, um tipo de valor pode declarar operadores definidos pelo usuário para a versão anulável do tipo. Ao determinar se um tipo pode declarar um determinado operador definido pelo usuário, os ? modificadores são primeiro descartados de todos os tipos envolvidos na declaração para fins de verificação de validade. Essa flexibilização não se aplica ao tipo de retorno dos operadores IsTrueIsFalse , eles ainda devem voltar Boolean, não Boolean?.
A precedência e a associatividade de um operador não podem ser modificadas por uma declaração do operador.
Nota. Os operadores têm a mesma restrição de colocação de linha que as sub-rotinas. A instrução inicial, a instrução final e o bloco devem aparecer no início de uma linha lógica.
Operadores Unary
Os seguintes operadores unários podem ser sobrecarregados:
O operador
+unário mais (método correspondente:op_UnaryPlus)O operador
-unário menos (método correspondente:op_UnaryNegation)O operador lógico
Not(método correspondente:op_OnesComplement)Os
IsTrueeIsFalseoperadores (métodos correspondentes:op_True,op_False)
Todos os operadores unários sobrecarregados devem tomar um único parâmetro do tipo que contém e podem retornar qualquer tipo, exceto para IsTrue e IsFalse, que deve retornar Boolean. Se o tipo que contém é um tipo genérico, os parâmetros de tipo devem corresponder aos parâmetros de tipo do tipo que contém. Por exemplo
Structure Complex
...
Public Shared Operator +(v As Complex) As Complex
Return v
End Operator
End Structure
Se um tipo sobrecarrega um ou IsTrueIsFalse, então ele deve sobrecarregar o outro também. Se apenas um estiver sobrecarregado, um erro em tempo de compilação resulta.
Nota.
IsTrue e IsFalse não são palavras reservadas.
Operadores binários
Os seguintes operadores binários podem ser sobrecarregados:
Os operadores de adição, subtração
-, multiplicação*, divisão/, divisão\integral, moduloMode exponenciação^(método correspondente:op_Addition,op_Subtraction,op_Multiply,op_Division,op_IntegerDivision,op_Exponentop_Modulus)+Os operadores
=relacionais , ,<><,>,<=, (>=métodos correspondentes:op_Equality,op_Inequality,op_LessThan, ,op_LessThanOrEqualop_GreaterThan,op_GreaterThanOrEqual). Nota. Enquanto o operador de igualdade pode ser sobrecarregado, o operador de atribuição (usado apenas em instruções de atribuição) não pode ser sobrecarregado.O
Likeoperador (método correspondente:op_Like)O operador
&de concatenação (método correspondente:op_Concatenate)O lógico
And,OreXoroperadores (métodos correspondentes:op_BitwiseAnd,op_BitwiseOr,op_ExclusiveOr)Os operadores de turnos
<<e>>(métodos correspondentes:op_LeftShift,op_RightShift)
Todos os operadores binários sobrecarregados devem tomar o tipo de contenção como um dos parâmetros. Se o tipo que contém é um tipo genérico, os parâmetros de tipo devem corresponder aos parâmetros de tipo do tipo que contém. Os operadores de turno restringem ainda mais esta regra para exigir que o primeiro parâmetro seja do tipo que contém; o segundo parâmetro deve ser sempre do tipo Integer.
Os seguintes operadores binários devem ser declarados em pares:
Operador
=e operador<>Operador
>e operador<Operador
>=e operador<=
Se um dos pares for declarado, o outro também deve ser declarado com parâmetros correspondentes e tipos de retorno, ou um erro em tempo de compilação resultará. (Observação. O objetivo de exigir declarações emparelhadas de operadores relacionais é tentar garantir pelo menos um nível mínimo de consistência lógica em operadores sobrecarregados.)
Em contraste com os operadores relacionais, sobrecarregar os operadores de divisão e de divisão integral é fortemente desencorajado, embora não seja um erro. (Observação. Em geral, os dois tipos de divisão devem ser totalmente distintos: um tipo que suporta a divisão é integral (caso em que deve apoiar \) ou não (caso em que deve apoiar /). Consideramos um erro definir ambos os operadores, mas como suas linguagens geralmente não distinguem entre dois tipos de divisão da mesma forma que o Visual Basic faz, sentimos que era mais seguro permitir a prática, mas fortemente desencorajá-la.)
Os operadores de atribuição compostos não podem ser sobrecarregados diretamente. Em vez disso, quando o operador binário correspondente estiver sobrecarregado, o operador de atribuição composto usará o operador sobrecarregado. Por exemplo:
Structure Complex
...
Public Shared Operator +(x As Complex, y As Complex) _
As Complex
...
End Operator
End Structure
Module Test
Sub Main()
Dim c1, c2 As Complex
' Calls the overloaded + operator
c1 += c2
End Sub
End Module
Operadores de Conversão
Os operadores de conversão definem novas conversões entre tipos. Essas novas conversões são chamadas de conversões definidas pelo usuário. Um operador de conversão converte de um tipo de fonte, indicado pelo tipo de parâmetro do operador de conversão, para um tipo de destino, indicado pelo tipo de retorno do operador de conversão. As conversões devem ser classificadas como alargamento ou estreitamento. Uma declaração de operador de conversão que inclui a Widening palavra-chave introduz uma conversão de alargamento definida pelo usuário (método correspondente: op_Implicit). Uma declaração de operador de conversão que inclui a Narrowing palavra-chave introduz uma conversão de estreitamento definida pelo usuário (método correspondente: op_Explicit).
Em geral, as conversões de ampliação definidas pelo usuário devem ser projetadas para nunca lançar exceções e nunca perder informações. Se uma conversão definida pelo usuário pode causar exceções (por exemplo, porque o argumento de origem está fora do intervalo) ou perda de informações (como descartar bits de ordem alta), essa conversão deve ser definida como uma conversão de estreitamento. No exemplo:
Structure Digit
Dim value As Byte
Public Sub New(value As Byte)
if value < 0 OrElse value > 9 Then Throw New ArgumentException()
Me.value = value
End Sub
Public Shared Widening Operator CType(d As Digit) As Byte
Return d.value
End Operator
Public Shared Narrowing Operator CType(b As Byte) As Digit
Return New Digit(b)
End Operator
End Structure
A conversão de para Byte é uma conversão de Digit alargamento porque nunca lança exceções ou perde informações, mas a conversão de para Digit é uma conversão de Byte estreitamento, uma vez que Digit só pode representar um subconjunto dos valores possíveis de um Byte.
Ao contrário de todos os outros membros do tipo que podem ser sobrecarregados, a assinatura de um operador de conversão inclui o tipo de destino da conversão. Este é o único membro do tipo para o qual o tipo de retorno participa da assinatura. No entanto, a classificação de alargamento ou estreitamento de um operador de conversão não faz parte da assinatura do operador. Assim, uma classe ou estrutura não pode declarar um operador de conversão de alargamento e um operador de conversão de estreitamento com os mesmos tipos de origem e destino.
Um operador de conversão definido pelo usuário deve converter para ou do tipo que contém -- por exemplo, é possível que uma classe C defina uma conversão de C para Integer e de Integer para C, mas não de Integer para Boolean. Se o tipo que contém é um tipo genérico, os parâmetros de tipo devem corresponder aos parâmetros de tipo do tipo que contém. Além disso, não é possível redefinir uma conversão intrínseca (ou seja, não definida pelo utilizador). Como resultado, um tipo não pode declarar uma conversão quando:
O tipo de origem e o tipo de destino são os mesmos.
Tanto o tipo de origem quanto o tipo de destino não são o tipo que define o operador de conversão.
O tipo de origem ou o tipo de destino é um tipo de interface.
O tipo de origem e os tipos de destino são relacionados por herança (incluindo
Object).
A única exceção a essas regras se aplica a tipos de valor anulável. Como os tipos de valor anulável não têm uma definição de tipo real, um tipo de valor pode declarar conversões definidas pelo usuário para a versão anulável do tipo. Ao determinar se um tipo pode declarar uma conversão específica definida pelo usuário, os ? modificadores são primeiro descartados de todos os tipos envolvidos na declaração para fins de verificação de validade. Assim, a seguinte declaração é válida porque S pode definir uma conversão de S para T:
Structure T
...
End Structure
Structure S
Public Shared Widening Operator CType(ByVal v As S?) As T
...
End Operator
End Structure
No entanto, a seguinte declaração não é válida porque a estrutura S não pode definir uma conversão de S para S:
Structure S
Public Shared Widening Operator CType(ByVal v As S) As S?
...
End Operator
End Structure
Mapeamento de operadores
Como o conjunto de operadores que o Visual Basic suporta pode não corresponder exatamente ao conjunto de operadores que outras linguagens no .NET Framework, alguns operadores são mapeados especialmente para outros operadores ao serem definidos ou usados. Mais especificamente:
A definição de um operador de divisão integral definirá automaticamente um operador de divisão regular (utilizável apenas de outros idiomas) que chamará o operador de divisão integral.
Sobrecarregando o
Not,AndeOros operadores sobrecarregarão apenas o operador bit a bit da perspetiva de outras linguagens que distinguem entre operadores lógicos e bitwise.Uma classe que sobrecarrega apenas os operadores lógicos em uma linguagem que distingue entre operadores lógicos e bitwise (ou seja, uma linguagem que usa
op_LogicalNot,op_LogicalAnd, eop_LogicalOrparaNot,Ande , e , respectivamenteOr) terá seus operadores lógicos mapeados nos operadores lógicos do Visual Basic. Se os operadores lógicos e bitwise estiverem sobrecarregados, apenas os operadores bitwise serão usados.Sobrecarregar os operadores e
>>sobrecarregará apenas os operadores assinados da perspetiva de outras linguagens que distinguem entre operadores de turno<<assinados e não assinados.Uma classe que sobrecarrega apenas um operador de turno não assinado terá o operador de turno não assinado mapeado no operador de turno correspondente do Visual Basic. Se um operador de turno não assinado e assinado estiver sobrecarregado, apenas o operador de turno assinado será usado.
Visual Basic language spec