Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este capítulo aborda uma série de conceitos que são necessários para entender a semântica da linguagem Microsoft Visual Basic. Muitos dos conceitos devem ser familiares para programadores do Visual Basic ou programadores C/C++, mas suas definições precisas podem ser diferentes.
Declarações
Um programa do Visual Basic é composto por entidades nomeadas. Essas entidades são introduzidas por meio de declarações e representam o "significado" do programa.
Em um nível superior, os namespaces são entidades que organizam outras entidades, como namespaces aninhados e tipos. Os tipos são entidades que descrevem valores e definem código executável. Os tipos podem conter tipos aninhados e membros de tipo. Os membros do tipo são constantes, variáveis, métodos, operadores, propriedades, eventos, valores de enumeração e construtores.
Uma entidade que pode conter outras entidades define um espaço de declaração. As entidades são introduzidas em um espaço de declaração por meio de declarações ou herança; o espaço de declaração que contém é chamado de contexto de declaração das entidades. Declarar uma entidade em um espaço de declaração, por sua vez, define um novo espaço de declaração que pode conter mais declarações de entidade aninhada; assim, as declarações em um programa formam uma hierarquia de espaços de declaração.
Exceto no caso de membros de tipo sobrecarregado, é inválido que as declarações introduzam entidades idênticas nomeadas do mesmo tipo no mesmo contexto de declaração. Além disso, um espaço de declaração pode nunca conter tipos diferentes de entidades com o mesmo nome; por exemplo, um espaço de declaração pode nunca conter uma variável e um método pelo mesmo nome.
Observação. Pode ser possível em outros idiomas criar um espaço de declaração que contenha diferentes tipos de entidades com o mesmo nome (por exemplo, se o idioma diferencia maiúsculas de minúsculas e permite declarações diferentes com base no casing). Nessa situação, a entidade mais acessível é considerada associada a esse nome; se mais de um tipo de entidade estiver mais acessível, o nome será ambíguo.
Public é mais acessível do que Protected Friend, Protected Friend é mais acessível do que Protected ou Friend, e Protected ou Friend é mais acessível do que Private.
O espaço de declaração de um namespace é "aberto encerrado", portanto, duas declarações de namespace com o mesmo nome totalmente qualificado contribuem para o mesmo espaço de declaração. No exemplo abaixo, as duas declarações de namespace contribuem para o mesmo espaço de declaração, nesse caso, declarando duas classes com os nomes Data.Customer totalmente qualificados e Data.Order:
Namespace Data
Class Customer
End Class
End Namespace
Namespace Data
Class Order
End Class
End Namespace
Como as duas declarações contribuem para o mesmo espaço de declaração, um erro de tempo de compilação ocorreria se cada uma contivesse uma declaração de uma classe com o mesmo nome.
Sobrecarga e assinaturas
A única maneira de declarar entidades idênticas nomeadas do mesmo tipo em um espaço de declaração é por sobrecarga. Somente métodos, operadores, construtores de instância e propriedades podem ser sobrecarregados.
Membros de tipo sobrecarregados devem possuir assinaturas exclusivas. A assinatura de um membro de tipo consiste no número de parâmetros de tipo e no número e tipos dos parâmetros do membro. Os operadores de conversão também incluem o tipo de retorno do operador na assinatura.
Os seguintes não fazem parte da assinatura de um membro e, portanto, não podem ser sobrecarregados em:
Modificadores para um membro de tipo (por exemplo,
SharedouPrivate).Modificadores para um parâmetro (por exemplo,
ByValouByRef).Os nomes dos parâmetros.
O tipo de retorno de um método ou operador (exceto para operadores de conversão) ou o tipo de elemento de uma propriedade.
Restrições em um parâmetro de tipo.
O exemplo a seguir mostra um conjunto de declarações de método sobrecarregadas junto com suas assinaturas. Essa declaração não seria válida, pois várias das declarações de método têm assinaturas idênticas.
Interface ITest
Sub F1() ' Signature is ().
Sub F2(x As Integer) ' Signature is (Integer).
Sub F3(ByRef x As Integer) ' Signature is (Integer).
Sub F4(x As Integer, y As Integer) ' Signature is (Integer, Integer).
Function F5(s As String) As Integer ' Signature is (String).
Function F6(x As Integer) As Integer ' Signature is (Integer).
Sub F7(a() As String) ' Signature is (String()).
Sub F8(ParamArray a() As String) ' Signature is (String()).
Sub F9(Of T)() ' Signature is !1().
Sub F10(Of T, U)(x As T, y As U) ' Signature is !2(!1, !2)
Sub F11(Of U, T)(x As T, y As U) ' Signature is !2(!2, !1)
Sub F12(Of T)(x As T) ' Signature is !1(!1)
Sub F13(Of T As IDisposable)(x As T) ' Signature is !1(!1)
End Interface
É válido definir um tipo genérico que pode conter membros com assinaturas idênticas com base nos argumentos de tipo fornecidos. As regras de resolução de sobrecarga são usadas para tentar desambiguar entre essas sobrecargas, embora possa haver situações em que é impossível desambiguar. Por exemplo:
Class C(Of T)
Sub F(x As Integer)
End Sub
Sub F(x As T)
End Sub
Sub G(Of U)(x As T, y As U)
End Sub
Sub G(Of U)(x As U, y As T)
End Sub
End Class
Module Test
Sub Main()
Dim x As New C(Of Integer)
x.F(10) ' Calls C(Of T).F(Integer)
x.G(Of Integer)(10, 10) ' Error: Can't choose between overloads
End Sub
End Module
Scope
O escopo do nome de uma entidade é o conjunto de todos os espaços de declaração nos quais é possível fazer referência a esse nome sem qualificação. Em geral, o escopo do nome de uma entidade é todo o contexto de declaração; no entanto, a declaração de uma entidade pode conter declarações aninhadas de entidades com o mesmo nome. Nesse caso, as sombras de entidade aninhadas ou ocultam a entidade externa e o acesso à entidade sombreada só é possível por meio da qualificação.
O sombreamento por meio do aninhamento ocorre em namespaces ou tipos aninhados em namespaces, em tipos aninhados em outros tipos e nos corpos dos membros do tipo. O sombreamento por meio do aninhamento de declarações sempre ocorre implicitamente; nenhuma sintaxe explícita é necessária.
No exemplo a seguir, dentro do F método, a variável i de instância é sombreada pela variável ilocal, mas dentro do G método, i ainda se refere à variável de instância.
Class Test
Private i As Integer = 0
Sub F()
Dim i As Integer = 1
End Sub
Sub G()
i = 1
End Sub
End Class
Quando um nome em um escopo interno oculta um nome em um escopo externo, ele sombreia todas as ocorrências sobrecarregadas desse nome. No exemplo a seguir, a chamada F(1) invoca o F declarado Inner porque todas as ocorrências externas F estão ocultas pela declaração interna. Pelo mesmo motivo, a chamada F("Hello") está em erro.
Class Outer
Shared Sub F(i As Integer)
End Sub
Shared Sub F(s As String)
End Sub
Class Inner
Shared Sub F(l As Long)
End Sub
Sub G()
F(1) ' Invokes Outer.Inner.F.
F("Hello") ' Error.
End Sub
End Class
End Class
Herança
Uma relação de herança é aquela em que um tipo (o tipo derivado ) deriva de outro (o tipo base ), de modo que o espaço de declaração do tipo derivado contém implicitamente os membros acessíveis do tipo não construtor e tipos aninhados de seu tipo base. No exemplo a seguir, a classe A é a classe base de B, e B é derivada de A.
Class A
End Class
Class B
Inherits A
End Class
Como A não especifica explicitamente uma classe base, sua classe base é implicitamente Object.
Veja a seguir aspectos importantes da herança:
A herança é transitiva. Se o tipo C for derivado do tipo B e o tipo B for derivado do tipo A, o tipo C herdará os membros do tipo declarados no tipo B , bem como os membros de tipo declarados no tipo A.
Um tipo derivado estende, mas não pode restringir, seu tipo base. Um tipo derivado pode adicionar novos membros de tipo e pode sombrear membros do tipo herdado, mas não pode remover a definição de um membro de tipo herdado.
Como uma instância de um tipo contém todos os membros de tipo de seu tipo base, sempre existe uma conversão de um tipo derivado para seu tipo base.
Todos os tipos devem ter um tipo base, exceto o tipo
Object. Portanto,Objecté o tipo base final de todos os tipos e todos os tipos podem ser convertidos nele.A circularidade na derivação não é permitida. Ou seja, quando um tipo
Bderiva de um tipoA, é um erro para o tipoAderivar direta ou indiretamente do tipoB.Um tipo pode não derivar direta ou indiretamente de um tipo aninhado dentro dele.
O exemplo a seguir produz um erro de tempo de compilação porque as classes dependem circularmente umas das outras.
Class A
Inherits B
End Class
Class B
Inherits C
End Class
Class C
Inherits A
End Class
O exemplo a seguir também produz um erro de tempo de compilação porque B deriva indiretamente de sua classe C aninhada por meio da classe A.
Class A
Inherits B.C
End Class
Class B
Inherits A
Public Class C
End Class
End Class
O exemplo a seguir não produz um erro porque a classe A não deriva da classe B.
Class A
Class B
Inherits A
End Class
End Class
Classes MustInherit e NotInheritable
Uma MustInherit classe é um tipo incompleto que pode atuar apenas como um tipo base. Uma MustInherit classe não pode ser instanciada, portanto, é um erro usar o New operador em um. É válido declarar variáveis de MustInherit classes; essas variáveis só podem ser atribuídas Nothing ou um valor que seja de uma classe derivada da MustInherit classe.
Quando uma classe regular é derivada de uma MustInherit classe, a classe regular deve substituir todos os membros herdados MustOverride . Por exemplo:
MustInherit Class A
Public MustOverride Sub F()
End Class
MustInherit Class B
Inherits A
Public Sub G()
End Sub
End Class
Class C
Inherits B
Public Overrides Sub F()
End Sub
End Class
A MustInherit classe A apresenta um MustOverride método F. A classe B introduz um método Gadicional, mas não fornece uma implementação de F. Portanto, a classe B também deve ser declarada MustInherit. A classe C substitui F e fornece uma implementação real. Como não há membros pendentes MustOverride na classe C, não é necessário ser MustInherit.
Uma NotInheritable classe é uma classe da qual outra classe não pode ser derivada.
NotInheritable as classes são usadas principalmente para evitar derivação não intencional.
Neste exemplo, a classe B está em erro porque tenta derivar da NotInheritable classe A. Uma classe não pode ser marcada como MustInheritNotInheritable.
NotInheritable Class A
End Class
Class B
' Error, a class cannot derive from a NotInheritable class.
Inherits A
End Class
Interfaces e várias heranças
Ao contrário de outros tipos, que derivam apenas de um único tipo base, uma interface pode derivar de várias interfaces base. Por isso, uma interface pode herdar um membro de tipo de nome idêntico de diferentes interfaces base. Nesse caso, o nome herdado por multiplicação não está disponível na interface derivada e fazer referência a qualquer um desses membros do tipo por meio da interface derivada causa um erro de tempo de compilação, independentemente de assinaturas ou sobrecarga. Em vez disso, membros de tipo conflitantes devem ser referenciados por meio de um nome de interface base.
No exemplo a seguir, as duas primeiras instruções causam erros de tempo de compilação porque o membro Count herdado por multiplicação não está disponível na interface IListCounter:
Interface IList
Property Count() As Integer
End Interface
Interface ICounter
Sub Count(i As Integer)
End Interface
Interface IListCounter
Inherits IList
Inherits ICounter
End Interface
Module Test
Sub F(x As IListCounter)
x.Count(1) ' Error, Count is not available.
x.Count = 1 ' Error, Count is not available.
CType(x, IList).Count = 1 ' Ok, invokes IList.Count.
CType(x, ICounter).Count(1) ' Ok, invokes ICounter.Count.
End Sub
End Module
Conforme ilustrado pelo exemplo, a ambiguidade é resolvida convertendo x para o tipo de interface base apropriado. Essas conversões não têm custos de tempo de execução; eles consistem apenas em exibir a instância como um tipo menos derivado no tempo de compilação.
Quando um membro de tipo único é herdado da mesma interface base por meio de vários caminhos, o membro do tipo é tratado como se tivesse sido herdado apenas uma vez. Em outras palavras, a interface derivada contém apenas uma instância de cada membro de tipo herdado de uma interface base específica. Por exemplo:
Interface IBase
Sub F(i As Integer)
End Interface
Interface ILeft
Inherits IBase
End Interface
Interface IRight
Inherits IBase
End Interface
Interface IDerived
Inherits ILeft, IRight
End Interface
Class Derived
Implements IDerived
' Only have to implement F once.
Sub F(i As Integer) Implements IDerived.F
End Sub
End Class
Se um nome de membro de tipo for sombreado em um caminho por meio da hierarquia de herança, o nome será sombreado em todos os caminhos. No exemplo a seguir, o IBase.F membro é sombreado pelo ILeft.F membro, mas não é sombreado em IRight:
Interface IBase
Sub F(i As Integer)
End Interface
Interface ILeft
Inherits IBase
Shadows Sub F(i As Integer)
End Interface
Interface IRight
Inherits IBase
Sub G()
End Interface
Interface IDerived
Inherits ILeft, IRight
End Interface
Class Test
Sub H(d As IDerived)
d.F(1) ' Invokes ILeft.F.
CType(d, IBase).F(1) ' Invokes IBase.F.
CType(d, ILeft).F(1) ' Invokes ILeft.F.
CType(d, IRight).F(1) ' Invokes IBase.F.
End Sub
End Class
A invocação d.F(1) é selecionada ILeft.F, embora IBase.F pareça não estar sombreada no caminho de acesso que leva até IRight. Como o caminho de acesso de IDerived para IBase sombrasIBase.F, o membro também é sombreado no caminho de acesso de IDerived até IRightIBase.ILeft
Sombreamento
Um tipo derivado sombreia o nome de um membro de tipo herdado declarando-o novamente. Sombrear um nome não remove os membros do tipo herdado com esse nome; ele simplesmente torna todos os membros do tipo herdado com esse nome indisponíveis na classe derivada. A declaração de sombreamento pode ser qualquer tipo de entidade.
Entidades que podem ser sobrecarregadas podem escolher uma das duas formas de sombreamento.
O sombreamento por nome é especificado usando a Shadows palavra-chave. Uma entidade que sombreia por nome oculta tudo por esse nome na classe base, incluindo todas as sobrecargas.
O sombreamento por nome e assinatura é especificado usando a Overloads palavra-chave. Uma entidade que sombreia por nome e assinatura oculta tudo por esse nome com a mesma assinatura que a entidade. Por exemplo:
Class Base
Sub F()
End Sub
Sub F(i As Integer)
End Sub
Sub G()
End Sub
Sub G(i As Integer)
End Sub
End Class
Class Derived
Inherits Base
' Only hides F(Integer).
Overloads Sub F(i As Integer)
End Sub
' Hides G() and G(Integer).
Shadows Sub G(i As Integer)
End Sub
End Class
Module Test
Sub Main()
Dim x As New Derived()
x.F() ' Calls Base.F().
x.G() ' Error: Missing parameter.
End Sub
End Module
Sombrear um método com um ParamArray argumento por nome e assinatura oculta apenas a assinatura individual, nem todas as assinaturas expandidas possíveis. Isso é verdadeiro mesmo se a assinatura do método de sombreamento corresponder à assinatura nãoexpanada do método sombreado. O exemplo a seguir:
Class Base
Sub F(ParamArray x() As Integer)
Console.WriteLine("Base")
End Sub
End Class
Class Derived
Inherits Base
Overloads Sub F(x() As Integer)
Console.WriteLine("Derived")
End Sub
End Class
Module Test
Sub Main
Dim d As New Derived()
d.F(10)
End Sub
End Module
imprime Base, embora tenha Derived.F a mesma assinatura que a forma nãoexpanada de Base.F.
Por outro lado, um método com um ParamArray argumento apenas sombreia métodos com a mesma assinatura, nem todas as assinaturas expandidas possíveis. O exemplo a seguir:
Class Base
Sub F(x As Integer)
Console.WriteLine("Base")
End Sub
End Class
Class Derived
Inherits Base
Overloads Sub F(ParamArray x() As Integer)
Console.WriteLine("Derived")
End Sub
End Class
Module Test
Sub Main()
Dim d As New Derived()
d.F(10)
End Sub
End Module
impressões Base, embora tenha Derived.F um formulário expandido que tenha a mesma assinatura Base.Fque .
Um método ou propriedade de sombreamento que não especifica Shadows ou Overloads pressupõe Overloads se o método ou propriedade for declarado Overrides, Shadows caso contrário. Se um membro de um conjunto de entidades sobrecarregadas especificar a palavra-chave ou Overloads a Shadows palavra-chave, todas elas deverão especificá-la. As Shadows palavras-chave e as Overloads palavras-chave não podem ser especificadas ao mesmo tempo. Nem Shadows nem Overloads pode ser especificado em um módulo padrão; membros em um módulo padrão implicitamente membros sombra herdados de Object.
É válido sombrear o nome de um membro de tipo que foi herdado por multiplicação por meio da herança da interface (e que, portanto, não está disponível), tornando o nome disponível na interface derivada.
Por exemplo:
Interface ILeft
Sub F()
End Interface
Interface IRight
Sub F()
End Interface
Interface ILeftRight
Inherits ILeft, IRight
Shadows Sub F()
End Interface
Module Test
Sub G(i As ILeftRight)
i.F() ' Calls ILeftRight.F.
CType(i, ILeft).F() ' Calls ILeft.F.
CType(i, IRight).F() ' Calls IRight.F.
End Sub
End Module
Como os métodos têm permissão para sombrear métodos herdados, é possível que uma classe contenha vários Overridable métodos com a mesma assinatura. Isso não apresenta um problema de ambiguidade, pois apenas o método mais derivado é visível. No exemplo a seguir, as classes e as C classes D contêm dois Overridable métodos com a mesma assinatura:
Class A
Public Overridable Sub F()
Console.WriteLine("A.F")
End Sub
End Class
Class B
Inherits A
Public Overrides Sub F()
Console.WriteLine("B.F")
End Sub
End Class
Class C
Inherits B
Public Shadows Overridable Sub F()
Console.WriteLine("C.F")
End Sub
End Class
Class D
Inherits C
Public Overrides Sub F()
Console.WriteLine("D.F")
End Sub
End Class
Module Test
Sub Main()
Dim d As New D()
Dim a As A = d
Dim b As B = d
Dim c As C = d
a.F()
b.F()
c.F()
d.F()
End Sub
End Module
Há dois Overridable métodos aqui: um introduzido por classe A e o introduzido por classe C. O método introduzido pela classe C oculta o método herdado da classe A. Assim, a Overrides declaração na classe D substitui o método introduzido pela classe Ce não é possível que a classe D substitua o método introduzido pela classe A. O exemplo produz a saída:
B.F
B.F
D.F
D.F
É possível invocar o método oculto Overridable acessando uma instância de classe D por meio de um tipo menos derivado no qual o método não está oculto.
Não é válido sombrear um MustOverride método, pois, na maioria dos casos, isso tornaria a classe inutilizável. Por exemplo:
MustInherit Class Base
Public MustOverride Sub F()
End Class
MustInherit Class Derived
Inherits Base
Public Shadows Sub F()
End Sub
End Class
Class MoreDerived
Inherits Derived
' Error: MustOverride method Base.F is not overridden.
End Class
Nesse caso, a classe MoreDerived é necessária para substituir o MustOverride métodoBase.F, mas como as sombras Base.Fde classeDerived, isso não é possível. Não há como declarar um descendente válido de Derived.
Em contraste com o sombreamento de um nome de um escopo externo, sombrear um nome acessível de um escopo herdado faz com que um aviso seja relatado, como no exemplo a seguir:
Class Base
Public Sub F()
End Sub
Private Sub G()
End Sub
End Class
Class Derived
Inherits Base
Public Sub F() ' Warning: shadowing an inherited name.
End Sub
Public Sub G() ' No warning, Base.G is not accessible here.
End Sub
End Class
A declaração do método F na classe Derived faz com que um aviso seja relatado. Sombrear um nome herdado não é especificamente um erro, pois isso impediria a evolução separada das classes base. Por exemplo, a situação acima pode ter ocorrido porque uma versão posterior da classe Base introduziu um método F que não estava presente em uma versão anterior da classe. Se a situação acima tivesse sido um erro, qualquer alteração feita em uma classe base em uma biblioteca de classes separadamente com versão poderia potencialmente fazer com que classes derivadas se tornassem inválidas.
O aviso causado pela sombra de um nome herdado pode ser eliminado por meio do uso do Shadows modificador ou Overloads do modificador:
Class Base
Public Sub F()
End Sub
End Class
Class Derived
Inherits Base
Public Shadows Sub F() 'OK.
End Sub
End Class
O Shadows modificador indica a intenção de sombrear o membro herdado. Não é um erro especificar o modificador ou Overloads o Shadows nome do membro do tipo a ser sombreado.
Uma declaração de um novo membro sombreia um membro herdado somente dentro do escopo do novo membro, como no exemplo a seguir:
Class Base
Public Shared Sub F()
End Sub
End Class
Class Derived
Inherits Base
Private Shared Shadows Sub F() ' Shadows Base.F in class Derived only.
End Sub
End Class
Class MoreDerived
Inherits Derived
Shared Sub G()
F() ' Invokes Base.F.
End Sub
End Class
No exemplo acima, a declaração do método F na classe Derived sombreia o método F que foi herdado da classe Base, mas como o novo método F na classe Derived tem Private acesso, seu escopo não se estende à classe MoreDerived. Assim, a chamada F() é MoreDerived.G válida e invocará Base.F. No caso de membros de tipo sobrecarregados, todo o conjunto de membros de tipo sobrecarregados é tratado como se todos tivessem o acesso mais permissivo para fins de sombreamento.
Class Base
Public Sub F()
End Sub
End Class
Class Derived
Inherits Base
Private Shadows Sub F()
End Sub
Public Shadows Sub F(i As Integer)
End Sub
End Class
Class MoreDerived
Inherits Derived
Public Sub G()
F() ' Error. No accessible member with this signature.
End Sub
End Class
Neste exemplo, embora a declaração de F() entrada Derived seja declarada com Private acesso, a sobrecarga F(Integer) é declarada com Public acesso. Portanto, para fins de sombreamento, o nome F em Derived é tratado como se fosse Public, de modo que ambos os métodos sombream F em Base.
Implementação
Existe uma relação de implementação quando um tipo declara que implementa uma interface e o tipo implementa todos os membros do tipo da interface. Um tipo que implementa uma interface específica é conversível para essa interface. As interfaces não podem ser instanciadas, mas é válido declarar variáveis de interfaces; essas variáveis só podem ser atribuídas a um valor de uma classe que implementa a interface. Por exemplo:
Interface ITestable
Function Test(value As Byte) As Boolean
End Interface
Class TestableClass
Implements ITestable
Function Test(value As Byte) As Boolean Implements ITestable.Test
Return value > 128
End Function
End Class
Module Test
Sub F()
Dim x As ITestable = New TestableClass
Dim b As Boolean
b = x.Test(34)
End Sub
End Module
Um tipo que implementa uma interface com membros de tipo herdado por multiplicação ainda deve implementar esses métodos, mesmo que eles não possam ser acessados diretamente da interface derivada que está sendo implementada. Por exemplo:
Interface ILeft
Sub Test()
End Interface
Interface IRight
Sub Test()
End Interface
Interface ILeftRight
Inherits ILeft, IRight
End Interface
Class LeftRight
Implements ILeftRight
' Has to reference ILeft explicitly.
Sub TestLeft() Implements ILeft.Test
End Sub
' Has to reference IRight explicitly.
Sub TestRight() Implements IRight.Test
End Sub
' Error: Test is not available in ILeftRight.
Sub TestLeftRight() Implements ILeftRight.Test
End Sub
End Class
Mesmo MustInherit as classes devem fornecer implementações de todos os membros de interfaces implementadas; no entanto, elas podem adiar a implementação desses métodos declarando-os como MustOverride. Por exemplo:
Interface ITest
Sub Test1()
Sub Test2()
End Interface
MustInherit Class TestBase
Implements ITest
' Provides an implementation.
Sub Test1() Implements ITest.Test1
End Sub
' Defers implementation.
MustOverride Sub Test2() Implements ITest.Test2
End Class
Class TestDerived
Inherits TestBase
' Have to implement MustOverride method.
Overrides Sub Test2()
End Sub
End Class
Um tipo pode optar por implementar novamente uma interface que seu tipo base implementa. Para implementar novamente a interface, o tipo deve declarar explicitamente que implementa a interface. Um tipo que implementa novamente uma interface pode optar por implementar novamente apenas alguns, mas não todos, dos membros da interface. Os membros não implementados novamente continuam a usar a implementação do tipo base. Por exemplo:
Class TestBase
Implements ITest
Sub Test1() Implements ITest.Test1
Console.WriteLine("TestBase.Test1")
End Sub
Sub Test2() Implements ITest.Test2
Console.WriteLine("TestBase.Test2")
End Sub
End Class
Class TestDerived
Inherits TestBase
Implements ITest ' Required to re-implement
Sub DerivedTest1() Implements ITest.Test1
Console.WriteLine("TestDerived.DerivedTest1")
End Sub
End Class
Module Test
Sub Main()
Dim Test As ITest = New TestDerived()
Test.Test1()
Test.Test2()
End Sub
End Module
Este exemplo imprime:
TestDerived.DerivedTest1
TestBase.Test2
Quando um tipo derivado implementa uma interface cujas interfaces base são implementadas pelos tipos base do tipo derivado, o tipo derivado pode optar por implementar apenas os membros do tipo da interface que ainda não foram implementados pelos tipos base. Por exemplo:
Interface IBase
Sub Base()
End Interface
Interface IDerived
Inherits IBase
Sub Derived()
End Interface
Class Base
Implements IBase
Public Sub Base() Implements IBase.Base
End Sub
End Class
Class Derived
Inherits Base
Implements IDerived
' Required: IDerived.Derived not implemented by Base.
Public Sub Derived() Implements IDerived.Derived
End Sub
End Class
Um método de interface também pode ser implementado usando um método substituível em um tipo base. Nesse caso, um tipo derivado também pode substituir o método substituível e alterar a implementação da interface. Por exemplo:
Class Base
Implements ITest
Public Sub Test1() Implements ITest.Test1
Console.WriteLine("TestBase.Test1")
End Sub
Public Overridable Sub Test2() Implements ITest.Test2
Console.WriteLine("TestBase.Test2")
End Sub
End Class
Class Derived
Inherits Base
' Overrides base implementation.
Public Overrides Sub Test2()
Console.WriteLine("TestDerived.Test2")
End Sub
End Class
Implementando métodos
Um tipo implementa um membro de tipo de uma interface implementada fornecendo um método com uma Implements cláusula. Os dois membros de tipo devem ter o mesmo número de parâmetros, todos os tipos e modificadores dos parâmetros devem corresponder, incluindo o valor padrão de parâmetros opcionais, o tipo de retorno deve corresponder e todas as restrições nos parâmetros do método devem corresponder. Por exemplo:
Interface ITest
Sub F(ByRef x As Integer)
Sub G(Optional y As Integer = 20)
Sub H(Paramarray z() As Integer)
End Interface
Class Test
Implements ITest
' Error: ByRef/ByVal mismatch.
Sub F(x As Integer) Implements ITest.F
End Sub
' Error: Defaults do not match.
Sub G(Optional y As Integer = 10) Implements ITest.G
End Sub
' Error: Paramarray does not match.
Sub H(z() As Integer) Implements ITest.H
End Sub
End Class
Um único método poderá implementar qualquer número de membros do tipo de interface se todos atenderem aos critérios acima. Por exemplo:
Interface ITest
Sub F(i As Integer)
Sub G(i As Integer)
End Interface
Class Test
Implements ITest
Sub F(i As Integer) Implements ITest.F, ITest.G
End Sub
End Class
Ao implementar um método em uma interface genérica, o método de implementação deve fornecer os argumentos de tipo que correspondem aos parâmetros de tipo da interface. Por exemplo:
Interface I1(Of U, V)
Sub M(x As U, y As List(Of V))
End Interface
Class C1(Of W, X)
Implements I1(Of W, X)
' W corresponds to U and X corresponds to V
Public Sub M(x As W, y As List(Of X)) Implements I1(Of W, X).M
End Sub
End Class
Class C2
Implements I1(Of String, Integer)
' String corresponds to U and Integer corresponds to V
Public Sub M(x As String, y As List(Of Integer)) _
Implements I1(Of String, Integer).M
End Sub
End Class
Observe que é possível que uma interface genérica não seja implementável para algum conjunto de argumentos de tipo.
Interface I1(Of T, U)
Sub S1(x As T)
Sub S1(y As U)
End Interface
Class C1
' Unable to implement because I1.S1 has two identical signatures
Implements I1(Of Integer, Integer)
End Class
Polimorfismo
O polimorfismo fornece a capacidade de variar a implementação de um método ou propriedade. Com o polimorfismo, o mesmo método ou propriedade pode executar ações diferentes dependendo do tipo de tempo de execução da instância que a invoca. Métodos ou propriedades polimórficas são chamados de substituíveis. Por outro lado, a implementação de um método ou propriedade não substituível é invariável; a implementação é a mesma se o método ou propriedade é invocado em uma instância da classe na qual ele é declarado ou uma instância de uma classe derivada. Quando um método ou propriedade não substituível é invocado, o tipo de tempo de compilação da instância é o fator determinante. Por exemplo:
Class Base
Public Overridable Property X() As Integer
Get
End Get
Set
End Set
End Property
End Class
Class Derived
Inherits Base
Public Overrides Property X() As Integer
Get
End Get
Set
End Set
End Property
End Class
Module Test
Sub F()
Dim Z As Base
Z = New Base()
Z.X = 10 ' Calls Base.X
Z = New Derived()
Z.X = 10 ' Calls Derived.X
End Sub
End Module
Um método substituível também pode ser MustOverride, o que significa que ele não fornece nenhum corpo do método e deve ser substituído.
MustOverride os métodos só são permitidos em MustInherit classes.
No exemplo a seguir, a classe Shape define a noção abstrata de um objeto de forma geométrica que pode se pintar:
MustInherit Public Class Shape
Public MustOverride Sub Paint(g As Graphics, r As Rectangle)
End Class
Public Class Ellipse
Inherits Shape
Public Overrides Sub Paint(g As Graphics, r As Rectangle)
g.drawEllipse(r)
End Sub
End Class
Public Class Box
Inherits Shape
Public Overrides Sub Paint(g As Graphics, r As Rectangle)
g.drawRect(r)
End Sub
End Class
O Paint método ocorre MustOverride porque não há nenhuma implementação padrão significativa. As classes Ellipse e Box são implementações concretas de Shape. Como essas classes não MustInheritsão, elas são necessárias para substituir o Paint método e fornecer uma implementação real.
É um erro para um acesso base fazer referência a um MustOverride método, como demonstra o exemplo a seguir:
MustInherit Class A
Public MustOverride Sub F()
End Class
Class B
Inherits A
Public Overrides Sub F()
MyBase.F() ' Error, MyBase.F is MustOverride.
End Sub
End Class
Um erro é relatado para a MyBase.F() invocação porque faz referência a um MustOverride método.
Substituindo métodos
Um tipo pode substituir um método herdado substituível declarando um método com o mesmo nome e assinatura e marcando a declaração com o Overrides modificador. Há requisitos adicionais sobre os métodos de substituição, listados abaixo. Enquanto uma declaração Overridable de método introduz um novo método, uma declaração Overrides de método substitui a implementação herdada do método.
Um método de substituição pode ser declarado NotOverridable, o que impede qualquer substituição adicional do método em tipos derivados. Na verdade, NotOverridable os métodos tornam-se não substituíveis em quaisquer classes derivadas adicionais.
Considere o seguinte exemplo:
Class A
Public Overridable Sub F()
Console.WriteLine("A.F")
End Sub
Public Overridable Sub G()
Console.WriteLine("A.G")
End Sub
End Class
Class B
Inherits A
Public Overrides NotOverridable Sub F()
Console.WriteLine("B.F")
End Sub
Public Overrides Sub G()
Console.WriteLine("B.G")
End Sub
End Class
Class C
Inherits B
Public Overrides Sub G()
Console.WriteLine("C.G")
End Sub
End Class
No exemplo, a classe B fornece dois Overrides métodos: um método F que tem o NotOverridable modificador e um método G que não tem. O uso do NotOverridable modificador impede que a classe C substitua ainda mais o método F.
Um método de substituição também pode ser declarado MustOverride, mesmo que o método que ele está substituindo não seja declarado MustOverride. Isso requer que a classe que contém seja declarada MustInherit e que quaisquer classes derivadas adicionais que não sejam declaradas MustInherit devem substituir o método. Por exemplo:
Class A
Public Overridable Sub F()
Console.WriteLine("A.F")
End Sub
End Class
MustInherit Class B
Inherits A
Public Overrides MustOverride Sub F()
End Class
No exemplo, a classe B substitui A.F por um MustOverride método. Isso significa que todas as classes derivadas B terão que substituir F, a menos que também sejam declaradas MustInherit .
Ocorre um erro de tempo de compilação, a menos que todos os seguintes procedimentos sejam verdadeiros para um método de substituição:
- O contexto de declaração contém um único método herdado acessível com o mesmo tipo de assinatura e retorno (se houver) que o método de substituição.
- O método herdado que está sendo substituído é substituível. Em outras palavras, o método herdado que está sendo substituído não
Sharedé ouNotOverridable. - O domínio de acessibilidade do método que está sendo declarado é o mesmo que o domínio de acessibilidade do método herdado que está sendo substituído. Há uma exceção: um
Protected Friendmétodo deve ser substituído por umProtectedmétodo se o outro método estiver em outro assembly ao qual o método de substituição não temFriendacesso. - Os parâmetros do método de substituição correspondem aos parâmetros do método substituído em relação ao uso dos
ByValmodificadores,ByRefParamArray,Optionalincluindo os valores fornecidos para parâmetros opcionais. - Os parâmetros de tipo do método de substituição correspondem aos parâmetros de tipo do método substituído em relação às restrições de tipo.
Ao substituir um método em um tipo genérico base, o método de substituição deve fornecer os argumentos de tipo que correspondem aos parâmetros de tipo base. Por exemplo:
Class Base(Of U, V)
Public Overridable Sub M(x As U, y As List(Of V))
End Sub
End Class
Class Derived(Of W, X)
Inherits Base(Of W, X)
' W corresponds to U and X corresponds to V
Public Overrides Sub M(x As W, y As List(Of X))
End Sub
End Class
Class MoreDerived
Inherits Derived(Of String, Integer)
' String corresponds to U and Integer corresponds to V
Public Overrides Sub M(x As String, y As List(Of Integer))
End Sub
End Class
Observe que é possível que um método substituível em uma classe genérica não possa ser substituído por alguns conjuntos de argumentos de tipo. Se o método for declarado MustOverride, isso significa que algumas cadeias de herança podem não ser possíveis. Por exemplo:
MustInherit Class Base(Of T, U)
Public MustOverride Sub S1(x As T)
Public MustOverride Sub S1(y As U)
End Class
Class Derived
Inherits Base(Of Integer, Integer)
' Error: Can't override both S1's at once
Public Overrides Sub S1(x As Integer)
End Sub
End Class
Uma declaração de substituição pode acessar o método base substituído usando um acesso base, como no exemplo a seguir:
Class Base
Private x As Integer
Public Overridable Sub PrintVariables()
Console.WriteLine("x = " & x)
End Sub
End Class
Class Derived
Inherits Base
Private y As Integer
Public Overrides Sub PrintVariables()
MyBase.PrintVariables()
Console.WriteLine("y = " & y)
End Sub
End Class
No exemplo, a invocação da MyBase.PrintVariables() classe Derived in invoca o PrintVariables método declarado na classe Base. Um acesso base desabilita o mecanismo de invocação substituível e simplesmente trata o método base como um método não substituível. Se a invocação tivesse Derived sido escrita CType(Me, Base).PrintVariables(), ela invocaria recursivamente o PrintVariables método declarado Derived, não o declarado em Base.
Somente quando ele inclui um Overrides modificador um método pode substituir outro método. Em todos os outros casos, um método com a mesma assinatura de um método herdado simplesmente sombreia o método herdado, como no exemplo abaixo:
Class Base
Public Overridable Sub F()
End Sub
End Class
Class Derived
Inherits Base
Public Overridable Sub F() ' Warning, shadowing inherited F().
End Sub
End Class
No exemplo, o método F na classe Derived não inclui um Overrides modificador e, portanto, não substitui o método F na classe Base. Em vez disso, o método F na classe Derived sombreia o método na classe Basee um aviso é relatado porque a declaração não inclui um Shadows ou Overloads modificador.
No exemplo a seguir, o método F na classe Derived sombreia o método F substituível herdado da classe Base:
Class Base
Public Overridable Sub F()
End Sub
End Class
Class Derived
Inherits Base
Private Shadows Sub F() ' Shadows Base.F within Derived.
End Sub
End Class
Class MoreDerived
Inherits Derived
Public Overrides Sub F() ' Ok, overrides Base.F.
End Sub
End Class
Como o novo método F na classe Derived tem Private acesso, seu escopo inclui apenas o corpo da Derived classe e não se estende à classe MoreDerived. Portanto, a declaração do método F na classe MoreDerived tem permissão para substituir o método F herdado da classe Base.
Quando um Overridable método é invocado, a implementação mais derivada do método de instância é chamada, com base no tipo da instância, independentemente de a chamada ser para o método na classe base ou na classe derivada. A implementação mais derivada de um Overridable método M em relação a uma classe R é determinada da seguinte maneira:
Se
Rcontiver a declaraçãoOverridablede introdução,Messa será a implementação mais derivada deM.Caso contrário, se
Rcontiver uma substituição,Messa será a implementação mais derivada deM.Caso contrário, a implementação
Mmais derivada é a mesma da classe base direta deR.
Acessibilidade
Uma declaração especifica a acessibilidade da entidade que ela declara. A acessibilidade de uma entidade não altera o escopo do nome de uma entidade. O domínio de acessibilidade de uma declaração é o conjunto de todos os espaços de declaração nos quais a entidade declarada está acessível.
Os cinco tipos de acesso são Public, , , Friende PrivateProtected FriendProtected.
Public é o tipo de acesso mais permissivo e os outros quatro tipos são todos subconjuntos de Public. O tipo de acesso menos permissivo é Private, e os outros quatro tipos de acesso são todos superconjuntos de Private.
AccessModifier
: 'Public'
| 'Protected'
| 'Friend'
| 'Private'
| 'Protected' 'Friend'
;
O tipo de acesso para uma declaração é especificado por meio de um modificador de acesso opcional, que pode ser Public, Protected, , Friendou Privatea combinação de Protected e Friend. Se nenhum modificador de acesso for especificado, o tipo de acesso padrão dependerá do contexto da declaração; os tipos de acesso permitidos também dependem do contexto da declaração.
As entidades declaradas com o
Publicmodificador têmPublicacesso. Não há restrições sobre o uso dePublicentidades.As entidades declaradas com o
Protectedmodificador têmProtectedacesso.ProtectedO acesso só pode ser especificado em membros de classes (membros de tipo regular e classes aninhadas) ou emOverridablemembros de módulos e estruturas padrão (que devem, por definição, ser herdados deSystem.ObjectouSystem.ValueType). UmProtectedmembro é acessível a uma classe derivada, desde que o membro não seja um membro de instância ou que o acesso ocorra por meio de uma instância da classe derivada.Protectedo acesso não é um superconjunto deFriendacesso.As entidades declaradas com o
Friendmodificador têmFriendacesso. Uma entidade comFriendacesso só pode ser acessada dentro do programa que contém a declaração de entidade ou os assemblies que receberamFriendacesso por meio doSystem.Runtime.CompilerServices.InternalsVisibleToAttributeatributo.As entidades declaradas com os
Protected Friendmodificadores têm a união eProtectedFriendo acesso.As entidades declaradas com o
Privatemodificador têmPrivateacesso. UmaPrivateentidade só pode ser acessada dentro de seu contexto de declaração, incluindo quaisquer entidades aninhadas.
A acessibilidade em uma declaração não depende da acessibilidade do contexto de declaração. Por exemplo, um tipo declarado com Private acesso pode conter um membro de tipo com Public acesso.
O código a seguir demonstra vários domínios de acessibilidade:
Public Class A
Public Shared X As Integer
Friend Shared Y As Integer
Private Shared Z As Integer
End Class
Friend Class B
Public Shared X As Integer
Friend Shared Y As Integer
Private Shared Z As Integer
Public Class C
Public Shared X As Integer
Friend Shared Y As Integer
Private Shared Z As Integer
End Class
Private Class D
Public Shared X As Integer
Friend Shared Y As Integer
Private Shared Z As Integer
End Class
End Class
As classes e os membros neste exemplo têm os seguintes domínios de acessibilidade:
O domínio de acessibilidade de
AeA.Xé ilimitado.O domínio de acessibilidade de
A.Y, ,B,B.XB.Y,B.C, eB.C.YB.C.Xé o programa que contém.O domínio de acessibilidade de
A.ZéA.O domínio de acessibilidade de , e
B.D.Xé , incluindoB.CeB.D.BB.D.YB.DB.ZO domínio de acessibilidade é
B.C.ZB.C.O domínio de acessibilidade é
B.D.ZB.D.
Como o exemplo ilustra, o domínio de acessibilidade de um membro nunca é maior do que o de um tipo que o contém. Por exemplo, embora todos os X membros tenham Public declarado acessibilidade, todos A.X têm domínios de acessibilidade restritos por um tipo que contém.
O acesso aos Protected membros da instância deve ser por meio de uma instância do tipo derivado para que os tipos não relacionados não possam ter acesso aos membros protegidos uns dos outros. Por exemplo:
Class User
Protected Password As String
End Class
Class Employee
Inherits User
End Class
Class Guest
Inherits User
Public Function GetPassword(u As User) As String
' Error: protected access has to go through derived type.
Return U.Password
End Function
End Class
No exemplo acima, a classe Guest só terá acesso ao campo protegido Password se for qualificada com uma instância de Guest. Isso impede Guest a obtenção de acesso ao campo de um Employee objeto simplesmente convertendo-o PasswordUserem .
Para fins de acesso de Protected membro em tipos genéricos, o contexto da declaração inclui parâmetros de tipo. Isso significa que um tipo derivado com um conjunto de argumentos de tipo não tem acesso aos Protected membros de um tipo derivado com um conjunto diferente de argumentos de tipo. Por exemplo:
Class Base(Of T)
Protected x As T
End Class
Class Derived(Of T)
Inherits Base(Of T)
Public Sub F(y As Derived(Of String))
' Error: Derived(Of T) cannot access Derived(Of String)'s
' protected members
y.x = "a"
End Sub
End Class
Observação. A linguagem C# (e possivelmente outros idiomas) permite que um tipo genérico acesse Protected membros, independentemente de quais argumentos de tipo são fornecidos. Isso deve ser mantido em mente ao criar classes genéricas que contêm Protected membros.
Tipos constituintes
Os tipos constituintes de uma declaração são os tipos referenciados pela declaração. Por exemplo, o tipo de uma constante, o tipo de retorno de um método e os tipos de parâmetro de um construtor são todos tipos constituintes. O domínio de acessibilidade de um tipo constituinte de uma declaração deve ser igual ou um superconjunto do domínio de acessibilidade da própria declaração. Por exemplo:
Public Class X
Private Class Y
End Class
' Error: Exposing private class Y outside of X.
Public Function Z() As Y
End Function
' Valid: Not exposing outside of X.
Private Function A() As Y
End Function
End Class
Friend Class B
Private Class C
End Class
' Error: Exposing private class Y outside of B.
Public Function D() As C
End Function
End Class
Nomes de tipo e namespace
Muitos constructos de linguagem exigem que um namespace ou tipo seja especificado; elas podem ser especificadas usando uma forma qualificada do namespace ou do nome do tipo. Um nome qualificado consiste em uma série de identificadores separados por períodos; o identificador no lado direito de um período é resolvido no espaço de declaração especificado pelo identificador no lado esquerdo do período.
O nome totalmente qualificado de um namespace ou tipo é um nome qualificado que contém o nome de todos os namespaces e tipos que contêm. Em outras palavras, o nome totalmente qualificado de um namespace ou tipo é N.T, onde T está o nome da entidade e N é o nome totalmente qualificado de sua entidade que contém.
O exemplo a seguir mostra vários namespaces e declarações de tipo junto com seus nomes totalmente qualificados associados em comentários em linha.
Class A ' A.
End Class
Namespace X ' X.
Class B ' X.B.
Class C ' X.B.C.
End Class
End Class
Namespace Y ' X.Y.
Class D ' X.Y.D.
End Class
End Namespace
End Namespace
Namespace X.Y ' X.Y.
Class E ' X.Y.E.
End Class
End Namespace
Observe que o namespace X.Y foi declarado em dois locais diferentes no código-fonte, mas essas duas declarações parciais constituem apenas um único namespace chamado X.Y que contém a classe D e a classe E.
Em algumas situações, um nome qualificado pode começar com a palavra-chave Global. A palavra-chave representa o namespace externo sem nome, que é útil em situações em que uma declaração sombreia um namespace delimitado. A Global palavra-chave permite "escape" para o namespace mais externo nessa situação. Por exemplo:
Namespace NS1
Class System
End Class
Module Test
Sub Main()
' Error: Class System does not contain Int32
Dim x As System.Int32
' Legal, binds to System in outermost namespace
Dim y As Global.System.Int32
End Sub
End Module
End Namespace
No exemplo acima, a primeira chamada de método é inválida porque o identificador System se associa à classe System, não ao namespace System. A única maneira de acessar o System namespace é usar Global para escapar para o namespace mais externo.
Global não pode ser usado em uma instrução Imports ou Namespace declaração.
Como outros idiomas podem introduzir tipos e namespaces que correspondem a palavras-chave no idioma, o Visual Basic reconhece palavras-chave como parte de um nome qualificado, desde que sigam um período. As palavras-chave usadas dessa forma são tratadas como identificadores. Por exemplo, o identificador X.Default.Class qualificado é um identificador qualificado válido, enquanto Default.Class não é.
Resolução de nomes qualificados para namespaces e tipos
Considerando um namespace qualificado ou um nome de tipo do formulário N.R(Of A), onde R é o identificador mais à direita no nome qualificado e A é uma lista de argumentos de tipo opcional, as etapas a seguir descrevem como determinar para qual namespace ou tipo o nome qualificado se refere:
Resolva
N, usando as regras para resolução de nomes qualificados ou não qualificados.Se a resolução de
Nfalha ou for resolvida para um parâmetro de tipo, ocorrerá um erro de tempo de compilação.Caso contrário, se
Rcorresponder ao nome de um namespace em N e nenhum argumento de tipo tiver sido fornecido, ouRcorresponder a um tipoNacessível com o mesmo número de parâmetros de tipo como argumentos de tipo, se houver, o nome qualificado se referirá a esse namespace ou tipo.Caso contrário, se
Ncontiver um ou mais módulos padrão eRcorresponder ao nome de um tipo acessível com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, em exatamente um módulo padrão, o nome qualificado se referirá a esse tipo. SeRcorresponder ao nome de tipos acessíveis com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, em mais de um módulo padrão, ocorrerá um erro de tempo de compilação.Do contrário, ocorrerá um erro em tempo de compilação.
Observação. Uma implicação desse processo de resolução é que os membros de tipo não sombream namespaces ou tipos ao resolver nomes de namespace ou de tipo.
Resolução de nomes não qualificada para namespaces e tipos
Dado um nome R(Of A)não qualificado, onde A está uma lista de argumentos de tipo opcional, as etapas a seguir descrevem como determinar para qual namespace ou tipo o nome não qualificado se refere:
Se R corresponder ao nome de um parâmetro de tipo do método atual e nenhum argumento de tipo tiver sido fornecido, o nome não qualificado se referirá a esse parâmetro de tipo.
Para cada tipo aninhado que contém a referência de nome, começando do tipo mais interno e indo para o mais externo:
- Se
Rcorresponder ao nome de um parâmetro de tipo no tipo atual e nenhum argumento de tipo tiver sido fornecido, o nome não qualificado se referirá a esse parâmetro de tipo. - Caso contrário, se
Rcorresponder ao nome de um tipo aninhado acessível com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, o nome não qualificado se referirá a esse tipo.
- Se
Para cada namespace aninhado que contém a referência de nome, começando do namespace mais interno e indo para o namespace mais externo:
- Se
Rcorresponder ao nome de um namespace aninhado no namespace atual e nenhuma lista de argumentos de tipo for fornecida, o nome não qualificado se referirá a esse namespace aninhado. - Caso contrário, se
Rcorresponder ao nome de um tipo acessível com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, no namespace atual, o nome não qualificado se referirá a esse tipo. - Caso contrário, se o namespace contiver um ou mais módulos padrão acessíveis e
Rcorresponder ao nome de um tipo aninhado acessível com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, em exatamente um módulo padrão, o nome não qualificado se referirá a esse tipo aninhado. SeRcorresponder ao nome de tipos aninhados acessíveis com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, em mais de um módulo padrão, ocorrerá um erro de tempo de compilação.
- Se
Se o arquivo de origem tiver um ou mais aliases de importação e
Rcorresponder ao nome de um deles, o nome não qualificado se referirá a esse alias de importação. Se uma lista de argumentos de tipo for fornecida, ocorrerá um erro de tempo de compilação.Se o arquivo de origem que contém a referência de nome tiver uma ou mais importações:
- Se
Rcorresponder ao nome de um tipo acessível com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, em exatamente uma importação, o nome não qualificado se referirá a esse tipo. SeRcorresponder ao nome de um tipo acessível com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, em mais de uma importação e todos não forem do mesmo tipo, ocorrerá um erro de tempo de compilação. - Caso contrário, se nenhuma lista de argumentos de tipo tiver sido fornecida e
Rcorresponder ao nome de um namespace com tipos acessíveis em exatamente uma importação, o nome não qualificado se referirá a esse namespace. Se nenhuma lista de argumentos de tipo tiver sido fornecida eRcorresponder ao nome de um namespace com tipos acessíveis em mais de uma importação e todos não forem o mesmo namespace, ocorrerá um erro de tempo de compilação. - Caso contrário, se as importações contiverem um ou mais módulos padrão acessíveis e
Rcorresponderem ao nome de um tipo aninhado acessível com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, em exatamente um módulo padrão, o nome não qualificado se referirá a esse tipo. SeRcorresponder ao nome de tipos aninhados acessíveis com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, em mais de um módulo padrão, ocorrerá um erro de tempo de compilação.
- Se
Se o ambiente de compilação definir um ou mais aliases de importação e
Rcorresponder ao nome de um deles, o nome não qualificado se referirá a esse alias de importação. Se uma lista de argumentos de tipo for fornecida, ocorrerá um erro de tempo de compilação.Se o ambiente de compilação definir uma ou mais importações:
- Se
Rcorresponder ao nome de um tipo acessível com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, em exatamente uma importação, o nome não qualificado se referirá a esse tipo. SeRcorresponder ao nome de um tipo acessível com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, em mais de uma importação, ocorrerá um erro de tempo de compilação. - Caso contrário, se nenhuma lista de argumentos de tipo tiver sido fornecida e
Rcorresponder ao nome de um namespace com tipos acessíveis em exatamente uma importação, o nome não qualificado se referirá a esse namespace. Se nenhuma lista de argumentos de tipo tiver sido fornecida eRcorresponder ao nome de um namespace com tipos acessíveis em mais de uma importação, ocorrerá um erro de tempo de compilação. - Caso contrário, se as importações contiverem um ou mais módulos padrão acessíveis e
Rcorresponderem ao nome de um tipo aninhado acessível com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, em exatamente um módulo padrão, o nome não qualificado se referirá a esse tipo. SeRcorresponder ao nome de tipos aninhados acessíveis com o mesmo número de parâmetros de tipo que argumentos de tipo, se houver, em mais de um módulo padrão, ocorrerá um erro de tempo de compilação.
- Se
Do contrário, ocorrerá um erro em tempo de compilação.
Observação. Uma implicação desse processo de resolução é que os membros de tipo não sombream namespaces ou tipos ao resolver nomes de namespace ou de tipo.
Normalmente, um nome só pode ocorrer uma vez em um namespace específico. No entanto, como os namespaces podem ser declarados em vários assemblies .NET, é possível ter uma situação em que dois assemblies definem um tipo com o mesmo nome totalmente qualificado. Nesse caso, um tipo declarado no conjunto atual de arquivos de origem é preferencial em vez de um tipo declarado em um assembly .NET externo. Caso contrário, o nome é ambíguo e não há como desambiguar o nome.
Variáveis
Uma variável representa um local de armazenamento. Cada variável tem um tipo que determina quais valores podem ser armazenados na variável. Como o Visual Basic é uma linguagem de tipo seguro, cada variável em um programa tem um tipo e a linguagem garante que os valores armazenados em variáveis sejam sempre do tipo apropriado. As variáveis são sempre inicializadas para o valor padrão de seu tipo antes que qualquer referência à variável possa ser feita. Não é possível acessar a memória não inicializada.
Tipos e métodos genéricos
Tipos (exceto módulos padrão e tipos enumerados) e métodos podem declarar parâmetros de tipo, que são tipos que não serão fornecidos até que uma instância do tipo seja declarada ou o método seja invocado. Tipos e métodos com parâmetros de tipo também são conhecidos como tipos genéricos e métodos genéricos, respectivamente, porque o tipo ou método deve ser escrito genericamente, sem conhecimento específico dos tipos que serão fornecidos pelo código que usa o tipo ou método.
Observação. No momento, embora métodos e delegados possam ser genéricos, propriedades, eventos e operadores não podem ser genéricos. Eles podem, no entanto, usar parâmetros de tipo da classe que contém.
Da perspectiva do tipo ou método genérico, um parâmetro de tipo é um tipo de espaço reservado que será preenchido com um tipo real quando o tipo ou método for usado. Os argumentos de tipo são substituídos pelos parâmetros de tipo no tipo ou método no ponto em que o tipo ou método é usado. Por exemplo, uma classe de pilha genérica pode ser implementada como:
Public Class Stack(Of ItemType)
Protected Items(0 To 99) As ItemType
Protected CurrentIndex As Integer = 0
Public Sub Push(data As ItemType)
If CurrentIndex = 100 Then
Throw New ArgumentException("Stack is full.")
End If
Items(CurrentIndex) = Data
CurrentIndex += 1
End Sub
Public Function Pop() As ItemType
If CurrentIndex = 0 Then
Throw New ArgumentException("Stack is empty.")
End If
CurrentIndex -= 1
Return Items(CurrentIndex + 1)
End Function
End Class
As declarações que usam a Stack(Of ItemType) classe devem fornecer um argumento de tipo para o parâmetro ItemTypede tipo. Esse tipo é preenchido onde quer que ItemType seja usado dentro da classe:
Option Strict On
Module Test
Sub Main()
Dim s1 As New Stack(Of Integer)()
Dim s2 As New Stack(Of Double)()
s1.Push(10.10) ' Error: Stack(Of Integer).Push takes an Integer
s2.Push(10.10) ' OK: Stack(Of Double).Push takes a Double
Console.WriteLine(s2.Pop().GetType().ToString()) ' Prints: Double
End Sub
End Module
Parâmetros de tipo
Os parâmetros de tipo podem ser fornecidos em declarações de tipo ou método. Cada parâmetro de tipo é um identificador que é um espaço reservado para um argumento de tipo fornecido para criar um tipo ou método construído. Por outro lado, um argumento de tipo é o tipo real que é substituído pelo parâmetro de tipo quando um tipo ou método genérico é usado.
TypeParameterList
: OpenParenthesis 'Of' TypeParameter ( Comma TypeParameter )* CloseParenthesis
;
TypeParameter
: VarianceModifier? Identifier TypeParameterConstraints?
;
VarianceModifier
: 'In' | 'Out'
;
Cada parâmetro de tipo em uma declaração de tipo ou método define um nome no espaço de declaração desse tipo ou método. Portanto, ele não pode ter o mesmo nome que outro parâmetro de tipo, um membro de tipo, um parâmetro de método ou uma variável local. O escopo de um parâmetro de tipo em um tipo ou método é todo o tipo ou método. Como os parâmetros de tipo têm escopo para toda a declaração de tipo, os tipos aninhados podem usar parâmetros de tipo externo. Isso também significa que os parâmetros de tipo devem ser sempre especificados ao acessar tipos aninhados dentro de tipos genéricos:
Public Class Outer(Of T)
Public Class Inner
Public Sub F(x As T)
...
End Sub
End Class
End Class
Module Test
Sub Main()
Dim x As New Outer(Of Integer).Inner()
...
End Sub
End Module
Ao contrário de outros membros de uma classe, os parâmetros de tipo não são herdados. Parâmetros de tipo em um tipo só podem ser referenciados pelo nome simples; em outras palavras, eles não podem ser qualificados com o nome de tipo que contém. Embora seja um estilo de programação inválido, os parâmetros de tipo em um tipo aninhado podem ocultar um membro ou parâmetro de tipo declarado no tipo externo:
Class Outer(Of T)
Class Inner(Of T)
Public t1 As T ' Refers to Inner's T
End Class
End Class
Tipos e métodos podem ser sobrecarregados com base no número de parâmetros de tipo (ou aridade) que os tipos ou métodos declaram. Por exemplo, as seguintes declarações são legais:
Module C
Sub M()
End Sub
Sub M(Of T)()
End Sub
Sub M(Of T, U)()
End Sub
End Module
Structure C(Of T)
Dim x As T
End Structure
Class C(Of T, U)
End Class
No caso de tipos, as sobrecargas são sempre correspondentes ao número de argumentos de tipo especificados. Isso é útil ao usar classes genéricas e não genéricas juntas no mesmo programa:
Class Queue
End Class
Class Queue(Of T)
End Class
Class X
Dim q1 As Queue ' Non-generic queue
Dim q2 As Queue(Of Integer) ' Generic queue
End Class
As regras para métodos sobrecarregados em parâmetros de tipo são abordadas na seção sobre a resolução de sobrecarga do método.
Dentro da declaração que contém, os parâmetros de tipo são considerados tipos completos. Como um parâmetro de tipo pode ser instanciado com muitos argumentos de tipo reais diferentes, os parâmetros de tipo têm operações e restrições ligeiramente diferentes dos outros tipos, conforme descrito abaixo:
Um parâmetro de tipo não pode ser usado diretamente para declarar uma classe base ou interface.
As regras para pesquisa de membro em parâmetros de tipo dependem das restrições, se houver, aplicadas ao parâmetro de tipo.
As conversões disponíveis para um parâmetro de tipo dependem das restrições, se houver, aplicadas aos parâmetros de tipo.
Na ausência de uma
Structurerestrição, um valor com um tipo representado por um parâmetro de tipo pode ser comparado comNothingo usoIseIsNot.Um parâmetro de tipo só poderá ser usado em uma
Newexpressão se o parâmetro de tipo for restringido por umaNewou umaStructurerestrição.Um parâmetro de tipo não pode ser usado em qualquer lugar dentro de uma exceção de atributo dentro de uma
GetTypeexpressão.Parâmetros de tipo podem ser usados como argumentos de tipo para outros tipos e parâmetros genéricos.
O exemplo a seguir é um tipo genérico que estende a Stack(Of ItemType) classe:
Class MyStack(Of ItemType)
Inherits Stack(Of ItemType)
Public ReadOnly Property Size() As Integer
Get
Return CurrentIndex
End Get
End Property
End Class
Quando uma declaração fornece um argumento de tipo, MyStacko mesmo argumento de tipo também será aplicado Stack .
Como um tipo, os parâmetros de tipo são puramente uma construção em tempo de compilação. Em tempo de execução, cada parâmetro de tipo é associado a um tipo de tempo de execução que foi especificado fornecendo um argumento de tipo para a declaração genérica. Assim, o tipo de uma variável declarada com um parâmetro de tipo será, em tempo de execução, um tipo não genérico ou um tipo construído específico. A execução em tempo de execução de todas as instruções e expressões que envolvem parâmetros de tipo usa o tipo real fornecido como o argumento de tipo para esse parâmetro.
Restrições de tipo
Como um argumento de tipo pode ser qualquer tipo no sistema de tipos, um tipo ou método genérico não pode fazer suposições sobre um parâmetro de tipo. Assim, os membros de um parâmetro de tipo são considerados os membros do tipo Object, pois todos os tipos derivam de Object.
No caso de uma coleção como Stack(Of ItemType), esse fato pode não ser uma restrição particularmente importante, mas pode haver casos em que um tipo genérico pode querer fazer uma suposição sobre os tipos que serão fornecidos como argumentos de tipo.
Restrições de tipo podem ser colocadas em parâmetros de tipo que restringem quais tipos podem ser fornecidos como um parâmetro de tipo e permitem que tipos ou métodos genéricos assumam mais sobre parâmetros de tipo.
TypeParameterConstraints
: 'As' Constraint
| 'As' OpenCurlyBrace ConstraintList CloseCurlyBrace
;
ConstraintList
: Constraint ( Comma Constraint )*
;
Constraint
: TypeName
| 'New'
| 'Structure'
| 'Class'
;
Public Class DisposableStack(Of ItemType As IDisposable)
Implements IDisposable
Private _items(0 To 99) As ItemType
Private _currentIndex As Integer = 0
Public Sub Push(data As ItemType)
...
End Sub
Public Function Pop() As ItemType
...
End Function
Private Sub Dispose() Implements IDisposable.Dispose
For Each item As IDisposable In _items
If item IsNot Nothing Then
item.Dispose()
End If
Next item
End Sub
End Class
Neste exemplo, o restrinja seu DisposableStack(Of ItemType) parâmetro de tipo a apenas tipos que implementam a interface System.IDisposable. Como resultado, ele pode implementar um Dispose método que descarta todos os objetos que ainda restam na fila.
Uma restrição de tipo deve ser uma das restrições ClassStructureespeciais ou Newdeve ser um tipo T em que:
Té uma classe, uma interface ou um parâmetro de tipo.TnãoNotInheritableé .Tnão é um dos tipos herdados de um dos seguintes tipos especiais:System.Array, ,System.Delegate,System.MulticastDelegate,System.EnumouSystem.ValueType.TnãoObjecté . Como todos os tipos derivam,Objectessa restrição não teria efeito se fosse permitida.Tdeve ser pelo menos tão acessível quanto o tipo genérico ou método que está sendo declarado.
Várias restrições de tipo podem ser especificadas para um único parâmetro de tipo colocando as restrições de tipo em chaves ({}). Apenas uma restrição de tipo para um determinado parâmetro de tipo pode ser uma classe. É um erro combinar uma Structure restrição especial com uma restrição de classe nomeada ou a Class restrição especial.
Class ControlFactory(Of T As {Control, New})
...
End Class
As restrições de tipo podem usar os tipos que contêm ou qualquer um dos parâmetros de tipo dos tipos que contêm. No exemplo a seguir, a restrição exige que o argumento de tipo fornecido implemente uma interface genérica usando-se como um argumento de tipo:
Class Sorter(Of V As IComparable(Of V))
...
End Class
A restrição Class de tipo especial restringe o argumento de tipo fornecido a qualquer tipo de referência.
Observação. A restrição Class de tipo especial pode ser atendida por uma interface. E uma estrutura pode implementar uma interface. Portanto, a restrição (Of T As U, U As Class) pode ser satisfeita com "T" uma estrutura (que não atende à Class restrição especial) e "U" uma interface que ela implementa (que atende à Class restrição especial).
A restrição Structure de tipo especial restringe o argumento de tipo fornecido a qualquer tipo de valor, exceto System.Nullable(Of T).
Observação. As restrições de estrutura não permitem System.Nullable(Of T) , de modo que não seja possível fornecer System.Nullable(Of T) como um argumento de tipo para si mesmo.
A restrição New de tipo especial exige que o argumento de tipo fornecido tenha um construtor sem parâmetros acessível e não possa ser declarado MustInherit. Por exemplo:
Class Factory(Of T As New)
Function CreateInstance() As T
Return New T()
End Function
End Class
Uma restrição de tipo de classe requer que o argumento de tipo fornecido seja esse tipo como ou herdado dele. Uma restrição de tipo de interface requer que o argumento de tipo fornecido implemente essa interface. Uma restrição de parâmetro de tipo requer que o argumento de tipo fornecido deva derivar ou implementar todos os limites fornecidos para o parâmetro de tipo correspondente. Por exemplo:
Class List(Of T)
Sub AddRange(Of S As T)(collection As IEnumerable(Of S))
...
End Sub
End Class
Neste exemplo, o parâmetro SAddRange de tipo é restrito ao parâmetro T de tipo de List. Isso significa que um List(Of Control) parâmetro de tipo 's restringiria AddRangea qualquer tipo que seja ou herda.Control
Uma restrição Of S As T de parâmetro de tipo é resolvida por meio da adição transitiva de todas as restrições de 's, Salém das Trestrições especiais (Class, Structure, New). É um erro ter restrições circulares (por exemplo, Of S As T, T As S). É um erro ter uma restrição de parâmetro de tipo que tem a restrição Structure em si. Depois de adicionar restrições, é possível que várias situações especiais possam ocorrer:
Se existirem várias restrições de classe, a classe mais derivada será considerada a restrição. Se uma ou mais restrições de classe não tiverem relação de herança, a restrição será insatisfeita e será um erro.
Se um parâmetro de tipo combinar uma
Structurerestrição especial com uma restrição de classe nomeada ou aClassrestrição especial, será um erro. Uma restrição de classe pode serNotInheritable, nesse caso, nenhum tipo derivado dessa restrição é aceito e é um erro.
O tipo pode ser um ou um tipo herdado dos seguintes tipos especiais: System.Array, , System.Delegate, System.MulticastDelegate, System.Enumou System.ValueType. Nesse caso, somente o tipo, ou um tipo herdado dele, é aceito. Um parâmetro de tipo restrito a um desses tipos só pode usar as conversões permitidas pelo DirectCast operador. Por exemplo:
MustInherit Class Base(Of T)
MustOverride Sub S1(Of U As T)(x As U)
End Class
Class Derived
Inherits Base(Of Integer)
' The constraint of U must be Integer, which is normally not allowed.
Overrides Sub S1(Of U As Integer)(x As U)
Dim y As Integer = x ' OK
Dim z As Long = x ' Error: Can't convert
End Sub
End Class
Além disso, um parâmetro de tipo restrito a um tipo de valor devido a um dos relaxamentos acima não pode chamar nenhum método definido nesse tipo de valor. Por exemplo:
Class C1(Of T)
Overridable Sub F(Of G As T)(x As G)
End Sub
End Class
Class C2
Inherits C1(Of IntPtr)
Overrides Sub F(Of G As IntPtr)(ByVal x As G)
' Error: Cannot access structure members
x.ToInt32()
End Sub
End Class
Se a restrição, após a substituição, terminar como um tipo de matriz, qualquer tipo de matriz covariante também será permitido. Por exemplo:
Module Test
Class B
End Class
Class D
Inherits B
End Class
Function F(Of T, U As T)(x As U) As T
Return x
End Function
Sub Main()
Dim a(9) As B
Dim b(9) As D
a = F(Of B(), D())(b)
End Sub
End Module
Um parâmetro de tipo com uma restrição de classe ou interface é considerado como tendo os mesmos membros que essa restrição de classe ou interface. Se um parâmetro de tipo tiver várias restrições, o parâmetro de tipo será considerado como sendo a união de todos os membros das restrições. Se houver membros com o mesmo nome em mais de uma restrição, os membros estarão ocultos na seguinte ordem: a restrição de classe oculta os membros em restrições de interface, que ocultam membros System.ValueType (se Structure a restrição for especificada), que oculta os membros em Object. Se um membro com o mesmo nome aparecer em mais de uma restrição de interface, o membro não estará disponível (como na herança de várias interfaces) e o parâmetro de tipo deverá ser convertido na interface desejada. Por exemplo:
Class C1
Sub S1(x As Integer)
End Sub
End Class
Interface I1
Sub S1(x As Integer)
End Interface
Interface I2
Sub S1(y As Double)
End Interface
Module Test
Sub T1(Of T As {C1, I1, I2})()
Dim a As T
a.S1(10) ' Calls C1.S1, which is preferred
a.S1(10.10) ' Also calls C1.S1, class is still preferred
End Sub
Sub T2(Of T As {I1, I2})()
Dim a As T
a.S1(10) ' Error: Call is ambiguous between I1.S1, I2.S1
End Sub
End Module
Ao fornecer parâmetros de tipo como argumentos de tipo, os parâmetros de tipo devem atender às restrições dos parâmetros de tipo correspondentes.
Class Base(Of T As Class)
End Class
Class Derived(Of V)
' Error: V does not satisfy the constraints of T
Inherits Base(Of V)
End Class
Os valores de um parâmetro de tipo restrito podem ser usados para acessar os membros da instância, incluindo métodos de instância, especificados na restrição.
Interface IPrintable
Sub Print()
End Interface
Class Printer(Of V As IPrintable)
Sub PrintOne(v1 As V)
V1.Print()
End Sub
End Class
Variação de parâmetro de tipo
Um parâmetro de tipo em uma interface ou uma declaração de tipo delegado pode, opcionalmente, especificar um modificador de variação. Parâmetros de tipo com modificadores de variação restringem como o parâmetro de tipo pode ser usado no tipo de interface ou delegado, mas permitem que uma interface genérica ou tipo de delegado seja convertido em outro tipo genérico com argumentos de tipo compatíveis com variantes. Por exemplo:
Class Base
End Class
Class Derived
Inherits Base
End Class
Module Test
Sub Main()
Dim x As IEnumerable(Of Derived) = ...
' OK, as IEnumerable(Of Base) is variant compatible
' with IEnumerable(Of Derived)
Dim y As IEnumerable(Of Base) = x
End Sub
End Module
Interfaces genéricas que têm parâmetros de tipo com modificadores de variação têm várias restrições:
Eles não podem conter uma declaração de evento que especifica uma lista de parâmetros (mas uma declaração de evento personalizada ou uma declaração de evento com um tipo delegado é permitida).
Eles não podem conter uma classe aninhada, uma estrutura ou um tipo enumerado.
Observação. Essas restrições se devem ao fato de que os tipos aninhados em tipos genéricos copiam implicitamente os parâmetros genéricos de seus pais. No caso de classes aninhadas, estruturas ou tipos enumerados, esses tipos não podem ter modificadores de variação em seus parâmetros de tipo. No caso de uma declaração de evento com uma lista de parâmetros, a classe delegada aninhada gerada pode ter erros confusos quando um tipo que parece ser usado em uma In posição (ou seja, um tipo de parâmetro) é realmente usado em uma Out posição (ou seja, o tipo do evento).
Um parâmetro de tipo declarado com o modificador Out é covariante. Informalmente, um parâmetro de tipo covariante só pode ser usado em uma posição de saída , ou seja, um valor que está sendo retornado do tipo de interface ou delegado e não pode ser usado em uma posição de entrada. Um tipo T será considerado válido de forma covariante se:
Té um tipo de classe, estrutura ou enumerado.Té o tipo de interface ou delegado não genérico.Té um tipo de matriz cujo tipo de elemento é válido de forma covariante.Té um parâmetro de tipo que não foi declarado como um parâmetro deOuttipo.Té um tipoX(Of P1,...,Pn)de interface ou delegado construído com argumentos deA1,...,Antipo de modo que:Se
Pifoi declarado como um parâmetro de tipo Out, entãoAié válido de forma covariante.Se
Pifoi declarado como um parâmetro de tipo In, entãoAié válido contravariantemente.
O seguinte deve ser válido de forma covariante em um tipo de interface ou delegado:
A interface base de uma interface.
O tipo de retorno de uma função ou do tipo delegado.
O tipo de uma propriedade se houver um
Getacessador.O tipo de qualquer
ByRefparâmetro.
Por exemplo:
Delegate Function D(Of Out T, U)(x As U) As T
Interface I1(Of Out T)
End Interface
Interface I2(Of Out T)
Inherits I1(Of T)
' OK, T is only used in an Out position
Function M1(x As I1(Of T)) As T
' Error: T is used in an In position
Function M2(x As T) As T
End Interface
Observação.
Out não é uma palavra reservada.
Um parâmetro de tipo declarado com o modificador In é contravariante. Informalmente, um parâmetro de tipo contravariante só pode ser usado em uma posição de entrada , ou seja, um valor que está sendo passado para o tipo de interface ou delegado e não pode ser usado em uma posição de saída. Um tipo T será considerado válido contravariantemente se:
Té um tipo de classe, estrutura ou enumerado.Té um delegado não genérico ou um tipo de interface.Té um tipo de matriz cujo tipo de elemento é válido contravariantemente.Té um parâmetro de tipo que não foi declarado como um parâmetro de tipo In.Té um tipoX(Of P1,...,Pn)de interface ou delegado construído com argumentos deA1,...,Antipo de modo que:Se
Pifoi declarado como umOutparâmetro de tipo, entãoAié válido contravariantemente.Se
Pifoi declarado como umInparâmetro de tipo, entãoAié válido covariantemente.
O seguinte deve ser válido contravariantemente em um tipo de interface ou delegado:
O tipo de um parâmetro.
Uma restrição de tipo em um parâmetro de tipo de método.
O tipo de uma propriedade se ela tiver um
Setacessador.O tipo de um evento.
Por exemplo:
Delegate Function D(Of T, In U)(x As U) As T
Interface I1(Of In T)
End Interface
Interface I2(Of In T)
' OK, T is only used in an In position
Sub M1(x As I1(Of T))
' Error: T is used in an Out position
Function M2() As T
End Interface
No caso em que um tipo deve ser válido de forma contravariante e covariante (como uma propriedade com um Get acessador ou Set um ByRef parâmetro), um parâmetro de tipo variante não pode ser usado.
A co-e contravariância dão origem a um "problema de ambiguidade de diamante". Considere o seguinte código:
Class C
Implements IEnumerable(Of String)
Implements IEnumerable(Of Exception)
Public Function GetEnumerator1() As IEnumerator(Of String) _
Implements IEnumerable(Of String).GetEnumerator
Console.WriteLine("string")
End Function
Public Function GetEnumerator2() As IEnumerator(Of Exception) _
Implements IEnumerable(Of Execption).GetEnumerator
Console.WriteLine("exception")
End Function
End Class
Dim c As IEnumerable(Of Object) = New C
c.GetEnumerator()
A classe C pode ser convertida IEnumerable(Of Object) de duas maneiras, tanto por meio da conversão covariante por meio da IEnumerable(Of String) conversão covariante de IEnumerable(Of Exception). O CLR não especifica quais dos dois métodos serão chamados por c.GetEnumerator(). Em geral, sempre que uma classe é declarada para implementar uma interface covariante com dois argumentos genéricos diferentes que têm um supertipo comum (por exemplo, nesse caso String e Exception têm o supertipo Objectcomum), ou uma classe é declarada para implementar uma interface contravariante com dois argumentos genéricos diferentes que têm um subtipo comum, é provável que a ambiguidade surja. O compilador fornece um aviso sobre essas declarações.
Visual Basic language spec