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.
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 Visual Basic ou programadores C/C++, mas suas definições precisas podem diferir.
Declarações
Um programa Visual Basic é composto de entidades nomeadas. Estas entidades são introduzidas através de declarações e representam o "significado" do programa.
Em um nível superior, namespaces são entidades que organizam outras entidades, como namespaces aninhados e tipos. 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 num espaço de declaração através de declarações ou herança; O espaço de declaração que contém é chamado de contexto de declaração das entidades. A declaração de 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 aninhadas; Assim, as declarações em um programa formam uma hierarquia de espaços de declaração.
Exceto no caso de membros de tipo sobrecarregados, é inválido que as declarações introduzam entidades com nomes idênticos do mesmo tipo no mesmo contexto de declaração. Além disso, um espaço de declaração nunca pode conter diferentes tipos de entidades com o mesmo nome; Por exemplo, um espaço de declaração pode nunca conter uma variável e um método com o mesmo nome.
Nota. Em outros idiomas, pode ser possível criar um espaço de declaração que contenha diferentes tipos de entidades com o mesmo nome (por exemplo, se o idioma diferenciar maiúsculas de minúsculas e permitir declarações diferentes com base em maiúsculas). Nessa situação, considera-se que a entidade mais acessível está vinculada a esse nome; Se mais de um tipo de entidade for mais acessível, o nome é ambíguo.
Publicé mais acessível do que , é mais acessível do que Protected FriendProtected ou Friend, e Protected ou Friend é mais acessível do que Private. Protected Friend
O espaço de declaração de um namespace é "open ended", 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, neste 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 em 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 com nomes idênticos do mesmo tipo em um espaço de declaração é por meio de sobrecarga. Somente métodos, operadores, construtores de instância e propriedades podem estar sobrecarregados.
Os membros do 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 de parâmetros do membro. Os operadores de conversão também incluem o tipo de retorno do operador na assinatura.
Os seguintes itens não fazem parte da assinatura de um membro e, portanto, não podem ser sobrecarregados:
Modificadores para um membro do tipo (por exemplo,
SharedouPrivate).Modificadores de 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 type.
O exemplo a seguir mostra um conjunto de declarações de método sobrecarregadas junto com suas assinaturas. Esta declaração não seria válida, uma vez que 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 seja 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
Âmbito de aplicação
O escopo do nome de uma entidade é o conjunto de todos os espaços de declaração dentro dos quais é possível fazer referência a esse nome sem qualificação. Em geral, o escopo do nome de uma entidade é todo o seu 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, a entidade aninhada sombreia, ou oculta, a entidade externa, e o acesso à entidade sombreada só é possível através da qualificação.
O sombreamento através do aninhamento ocorre em namespaces ou tipos aninhados em namespaces, em tipos aninhados em outros tipos e nos corpos de membros do tipo. O sombreamento através do aninhamento de declarações ocorre sempre 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 declarado F em Inner porque todas as ocorrências externas de F estão ocultas pela declaração interna. Pelo mesmo motivo, a chamada F("Hello") está errada.
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 na qual 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 os tipos aninhados de seu tipo base. No exemplo a seguir, class A é a classe base de , e B é derivada Bde 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.
São aspetos importantes da herança:
A herança é transitiva. Se o tipo C é derivado do tipo B, e o tipo B é derivado do tipo A, o tipo C herda os membros do tipo declarados no tipo B , bem como os membros do 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 de tipo herdados, 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 do 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. Assim,Objecté o tipo de base final de todos os tipos, e todos os tipos podem ser convertidos para ele.Não é permitida circularidade na derivação. Ou seja, quando um tipo
Bderiva de um tipoA, é um erro para o tipoAderivar direta ou indiretamente do tipoB.Um tipo não pode derivar, direta ou indiretamente, de um tipo aninhado nele.
O exemplo a seguir produz um erro em 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 em tempo de compilação porque B deriva indiretamente de sua classe C aninhada através da classe A.
Class A
Inherits B.C
End Class
Class B
Inherits A
Public Class C
End Class
End Class
O próximo exemplo 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 uma. É válido declarar variáveis de MustInherit classes, tais 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 introduz um MustOverride método F. Class B introduz um método Gadicional, mas não fornece uma implementação de F. Por conseguinte, a classe B também deve ser declarada MustInherit. A classe C substitui F e fornece uma implementação real. Uma vez que 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 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 MustInheritNotInheritablee .
NotInheritable Class A
End Class
Class B
' Error, a class cannot derive from a NotInheritable class.
Inherits A
End Class
Interfaces e herança múltipla
Ao contrário de outros tipos, que derivam apenas de um único tipo de base, uma interface pode derivar de várias interfaces de base. Devido a isso, uma interface pode herdar um membro de tipo com nome idêntico de diferentes interfaces base. Nesse caso, o nome herdado de multiplicação não está disponível na interface derivada, e referir-se a qualquer um desses membros do tipo através da interface derivada causa um erro em tempo de compilação, independentemente de assinaturas ou sobrecarga. Em vez disso, os 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 em tempo de compilação porque o membro Count multiply-inherited 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
Como ilustrado pelo exemplo, a ambiguidade é resolvida convertendo x para o tipo de interface base apropriado. Tais moldes não têm custos de tempo de execução; eles consistem apenas em ver a instância como um tipo menos derivado em tempo de compilação.
Quando um único membro do tipo é herdado da mesma interface base através de vários caminhos, o membro do tipo é tratado como se fosse herdado apenas uma vez. Em outras palavras, a interface derivada contém apenas uma instância de cada membro do 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 do tipo for sombreado em um caminho através da hierarquia de herança, o nome será sombreado em todos os caminhos. No exemplo a seguir, o IBase.F membro é sombreado pelo membro, mas não é sombreado ILeft.F 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) seleciona ILeft.F, mesmo IBase.F que pareça não estar sombreada no caminho de acesso que conduz pelo IRight. Como o caminho de acesso de para para sombrasIBase.F, o membro também é sombreado no caminho de acesso de IDerived para IRightIBase.IBaseILeftIDerived
Sombreamento
Um tipo derivado sombreia o nome de um membro de tipo herdado declarando-o novamente. O sombreamento de um nome não remove os membros do tipo herdados com esse nome; ele simplesmente torna todos os membros do tipo herdados com esse nome indisponíveis na classe derivada. A declaração de sombreamento pode ser qualquer tipo de entidade.
As entidades que podem ser sobrecarregadas podem escolher uma das duas formas de sombreamento.
O sombreamento por nome é especificado usando a palavra-chave Shadows . 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, não todas as assinaturas expandidas possíveis. Isso é verdadeiro mesmo se a assinatura do método de sombreamento corresponder à assinatura não expandida 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
Baseimprime , embora Derived.F tenha a mesma assinatura que a forma não expandida de Base.F.
Por outro lado, um método com um ParamArray argumento apenas sombreia métodos com a mesma assinatura, não 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
Baseimprime , mesmo Derived.F que tenha uma forma expandida que tenha a mesma assinatura que Base.F.
Um método de sombreamento ou propriedade que não especifica Shadows ou Overloads assume Overloads se o método ou propriedade é declarado Overrides, Shadows caso contrário. Se um membro de um conjunto de entidades sobrecarregadas especificar a Shadows palavra-chave ou Overloads , todos eles deverão especificá-la. As Shadows palavras-chave e Overloads não podem ser especificadas ao mesmo tempo. Nem ShadowsOverloads podem ser especificados em um módulo padrão, membros em um módulo padrão implicitamente sombra membros herdados de Object.
É válido sombrear o nome de um membro do tipo que foi multiplicado herdado através da herança da interface (e que, portanto, não está disponível), tornando assim 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. Isto não apresenta um problema de ambiguidade, uma vez que apenas o método mais derivado é visível. No exemplo a seguir, as C classes e 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 outro introduzido por classe C. O método introduzido pela classe C oculta o método herdado da classe A. Assim, a Overrides declaração em classe D substitui o método introduzido por classe C, e não é possível para D classe substituir o método introduzido por 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 através de um tipo menos derivado no qual o método não está oculto.
Não é válido fazer sombra a um MustOverride método, porque 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étodo Base.F, mas como a classe Derived sombras Base.F, isso não é possível. Não há como declarar um descendente válido de Derived.
Em contraste com sombrear 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 especificamente não é um erro, uma vez que isso impediria a evolução separada das classes base. Por exemplo, a situação acima pode ter surgido 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 fosse um erro, qualquer alteração feita em uma classe base em uma biblioteca de classes com versões separadas poderia potencialmente fazer com que as classes derivadas se tornassem inválidas.
O aviso causado pelo sombreamento de um nome herdado pode ser eliminado através do uso do Shadows modificador ou Overloads :
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 Shadows modificador ou Overloads se não houver nenhum nome de membro do tipo para sombra.
Uma declaração de um novo membro faz sombra a um membro herdado apenas 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 de 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, mesmo que a declaração de in Derived seja declarada com Private acesso, a sobrecarregada F(Integer) é declarada F() com Public acesso. Portanto, para fins de sombreamento, o nome F em Derived é tratado como se fosse Public, então ambos os métodos sombreiam F em Base.
Execuçã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 que seja de uma classe que implemente 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 herdados de 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 das interfaces implementadas, no entanto, eles 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 reimplementar uma interface que seu tipo base implementa. Para reimplementar a interface, o tipo deve declarar explicitamente que implementa a interface. Um tipo que reimplementa uma interface pode optar por reimplementar apenas alguns, mas não todos, dos membros da interface -- todos os membros não reimplementados 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 de 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
Métodos de implementação
Um tipo implementa um tipo membro de uma interface implementada fornecendo um método com uma Implements cláusula. Os dois membros do 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 pode implementar qualquer número de membros do tipo de interface se todos eles 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 o invoca. Métodos ou propriedades que são polimórficos são chamados substituíveis. Em contrapartida, 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 é 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 de método e deve ser substituído.
MustOverride Os métodos só são permitidos nas MustInherit aulas.
No exemplo a seguir, a classe Shape define a noção abstrata de um objeto de forma geométrica que pode pintar a si mesmo:
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 é 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 de base fazer referência a um MustOverride método, como o exemplo a seguir demonstra:
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 invocação porque faz referência a MyBase.F() um MustOverride método.
Métodos de substituição
Um tipo pode substituir um método substituível herdado declarando um método com o mesmo nome e assinatura e marcando a declaração com o Overrides modificador. Existem requisitos adicionais sobre métodos de substituição, listados abaixo. Enquanto uma Overridable declaração de método introduz um novo método, uma Overrides declaração de método substitui a implementação herdada do método.
Pode ser declarado NotOverridableum método de neutralização , que impeça qualquer nova substituição do método em tipos derivados. Com efeito, NotOverridable os métodos tornam-se não substituíveis em quaisquer outras classes derivadas.
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 modificador impede que a NotOverridable 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 outras classes derivadas que não sejam declaradas MustInherit substituam 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 por A.F um MustOverride método. Isso significa que quaisquer classes derivadas de terão que substituir F, a menos que também sejam declaradas.BMustInherit
Um erro em tempo de compilação ocorre a menos que todos os itens a seguir sejam verdadeiros de um método de substituição:
- O contexto da declaração contém um único método herdado acessível com a mesma assinatura e tipo de retorno (se houver) que o método de substituição.
- O método herdado que está sendo substituído é substituído. 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 tenhaFriendacesso. - Os parâmetros do método de substituição correspondem aos parâmetros do método substituído em relação ao uso do
ByVal,ByRefParamArray,eOptionalmodificadores, incluindo 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 do 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 para 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 de in class invoca o PrintVariables método declarado MyBase.PrintVariables() em class Base.Derived 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 sido escritaCType(Me, Base).PrintVariables(), invocaria recursivamente o método declarado PrintVariables em Derived, e não o declarado em Base.Derived
Somente quando ele inclui um Overrides modificador pode um método 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 Base, e um aviso é relatado porque a declaração não inclui um Shadows modificador ou Overloads .
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 classe de Derived e não se estende à classe MoreDerived. A declaração do método F em classe MoreDerived é, portanto, permitida 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 forma:
Se
Rcontém a declaração introdutóriaOverridablede , esta é a implementação mais derivadaMdeM.Caso contrário, se
Rcontiver uma substituição de , esta é a implementação mais derivadaMdoM.Caso contrário, a implementação mais derivada de
Mé a mesma que a da classe base direta deR.
Acessibilidade
Uma declaração especifica a acessibilidade da entidade que 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, , , FriendProtected Friend, e PrivateProtected.
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 através de um modificador de acesso opcional, que pode ser , , , Private, ou a combinação de Protected e Friend. FriendProtectedPublic 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 existem restrições à utilização 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 da instância ou 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 é acessível somente dentro do programa que contém a declaração de entidade ou quaisquer assemblies que receberamFriendacesso por meio doSystem.Runtime.CompilerServices.InternalsVisibleToAttributeatributo.As entidades declaradas com os
Protected Friendmodificadores têm a união deProtectedeFriendacesso.As entidades declaradas com o
Privatemodificador têmPrivateacesso. UmaPrivateentidade só é acessível dentro do seu contexto de declaração, incluindo quaisquer entidades aninhadas.
A acessibilidade numa declaração não depende da acessibilidade do contexto da declaração. Por exemplo, um tipo declarado com Private acesso pode conter um membro do 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 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.X,B.YB.C,B.C.X, eB.C.Yé o programa que contém.O domínio de acessibilidade de
A.ZéA.O domínio de acessibilidade de , , , e
B.D.YéB, incluindoB.CeB.D.B.D.XB.DB.ZO domínio de acessibilidade de
B.C.ZéB.C.O domínio de acessibilidade de
B.D.ZéB.D.
Como o exemplo ilustra, o domínio de acessibilidade de um membro nunca é maior do que o de um tipo de contenção. Por exemplo, mesmo que todos os X membros tenham Public declarado acessibilidade, todos, exceto A.X têm domínios de acessibilidade que são limitados por um tipo de contenção.
O acesso aos Protected membros da instância deve ser feito por meio de uma instância do tipo derivado para que tipos não relacionados não possam obter 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ó tem acesso ao campo protegido Password se estiver qualificada com uma instância de Guest. Isso impede Guest o acesso ao Password campo de um Employee objeto simplesmente lançando-o para User.
Para fins de acesso de membros em tipos genéricos, o contexto da declaração inclui parâmetros de Protected 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
Nota. A linguagem C# (e possivelmente outras linguagens) permite que um tipo genérico acesse Protected membros, independentemente de quais argumentos de tipo são fornecidos. Isso deve ser tido em mente ao projetar classes genéricas que contenham Protected membros.
Tipos de Constituintes
Os tipos constitutivos 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âmetros de um construtor são todos os tipos constituintes. O domínio de acessibilidade de um tipo constituinte de uma declaração deve ser o mesmo 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
Muitas construções de linguagem exigem que um namespace ou tipo seja especificado; eles podem ser especificados usando uma forma qualificada do namespace ou nome do tipo. Um nome qualificado consiste numa série de identificadores separados por pontos; 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 é o nome da entidade e N é o nome totalmente qualificado de sua entidade que a contém.
O exemplo abaixo mostra várias declarações de namespace e tipo juntamente com seus nomes totalmente qualificados associados em comentários in-line.
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 anexo. A Global palavra-chave permite "escapar" para o namespace 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 liga à 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 utilizado numa Imports declaração ou Namespace declaração.
Como outras linguagens podem introduzir tipos e namespaces que correspondem a palavras-chave na linguagem, o Visual Basic reconhece palavras-chave como parte de um nome qualificado, desde que sigam um ponto. As palavras-chave utilizadas desta 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
Dado um namespace qualificado ou 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 a qual namespace ou tipo o nome qualificado se refere:
Resolver
N, usando as regras para resolução de nomes qualificados ou não qualificados.Se a resolução falhar ou resolver para um parâmetro type, ocorrerá um erro em tempo de
Ncompilação.Caso contrário, se
Rcorresponder ao nome de um namespace em N e nenhum argumento de tipo tiver sido fornecido, ouRcorresponder a um tipo acessível comNo mesmo número de parâmetros de tipo que os argumentos de tipo, se houver, o nome qualificado se refere 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 os argumentos de tipo, se houver, em exatamente um módulo padrão, então o nome qualificado refere-se a esse tipo. SeRcorresponder ao nome de tipos acessíveis com o mesmo número de parâmetros de tipo que os argumentos de tipo, se houver, em mais de um módulo padrão, ocorrerá um erro em tempo de compilação.Caso contrário, ocorrerá um erro em tempo de compilação.
Nota. Uma implicação desse processo de resolução é que os membros do tipo não sombreiam namespaces ou tipos ao resolver nomes de namespace ou tipo.
Resolução de nomes não qualificada para namespaces e tipos
Dado um nome R(Of A)não qualificado , onde A é uma lista de argumentos de tipo opcional, as etapas a seguir descrevem como determinar a 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 refere a esse parâmetro de tipo.
Para cada tipo aninhado que contém a referência de nome, começando pelo 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 refere 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 os argumentos de tipo, se houver, o nome não qualificado refere-se a esse tipo.
- Se
Para cada namespace aninhado que contém a referência de nome, começando pelo 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 os argumentos de tipo, se houver, no namespace atual, o nome não qualificado se refere 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 os argumentos de tipo, se houver, em exatamente um módulo padrão, o nome não qualificado se refere a esse tipo aninhado. SeRcorresponder ao nome de tipos aninhados acessíveis com o mesmo número de parâmetros de tipo que os argumentos de tipo, se houver, em mais de um módulo padrão, ocorrerá um erro em 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 refere a esse alias de importação. Se uma lista de argumentos de tipo for fornecida, ocorrerá um erro em tempo de compilação.Se o ficheiro 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 os argumentos de tipo, se houver, em exatamente uma importação, então o nome não qualificado refere-se a esse tipo. SeRcorresponder ao nome de um tipo acessível com o mesmo número de parâmetros de tipo que os argumentos de tipo, se houver, em mais de uma importação e todos não forem do mesmo tipo, ocorrerá um erro em tempo de compilação. - Caso contrário, se nenhuma lista de argumentos de tipo foi fornecida e
Rcorresponde ao nome de um namespace com tipos acessíveis em exatamente uma importação, o nome não qualificado se refere a esse namespace. Se nenhuma lista de argumentos de tipo foi fornecida eRcorresponde ao nome de um namespace com tipos acessíveis em mais de uma importação e todos não são o mesmo namespace, ocorre um erro em 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 os argumentos de tipo, se houver, em exatamente um módulo padrão, o nome não qualificado se refere a esse tipo. SeRcorresponder ao nome de tipos aninhados acessíveis com o mesmo número de parâmetros de tipo que os argumentos de tipo, se houver, em mais de um módulo padrão, ocorrerá um erro em 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 refere a esse alias de importação. Se uma lista de argumentos de tipo for fornecida, ocorrerá um erro em 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 os argumentos de tipo, se houver, em exatamente uma importação, então o nome não qualificado refere-se a esse tipo. SeRcorresponder ao nome de um tipo acessível com o mesmo número de parâmetros de tipo que os argumentos de tipo, se houver, em mais de uma importação, ocorrerá um erro em tempo de compilação. - Caso contrário, se nenhuma lista de argumentos de tipo foi fornecida e
Rcorresponde ao nome de um namespace com tipos acessíveis em exatamente uma importação, o nome não qualificado se refere a esse namespace. Se nenhuma lista de argumentos de tipo foi fornecida eRcorresponde ao nome de um namespace com tipos acessíveis em mais de uma importação, ocorre um erro em 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 os argumentos de tipo, se houver, em exatamente um módulo padrão, o nome não qualificado se refere a esse tipo. SeRcorresponder ao nome de tipos aninhados acessíveis com o mesmo número de parâmetros de tipo que os argumentos de tipo, se houver, em mais de um módulo padrão, ocorrerá um erro em tempo de compilação.
- Se
Caso contrário, ocorrerá um erro em tempo de compilação.
Nota. Uma implicação desse processo de resolução é que os membros do tipo não sombreiam namespaces ou tipos ao resolver nomes de namespace ou 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 é preferível a 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 segura para tipos, 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 com 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 para 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.
Nota. Neste momento, embora os métodos e delegados possam ser genéricos, as propriedades, eventos e operadores não podem ser genéricos. Podem, no entanto, utilizar parâmetros de tipo da classe que os contém.
Da perspetiva do tipo ou método genérico, um parâmetro type é 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 type para o parâmetro ItemTypetype . Este tipo é então 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 type é um identificador que é um espaço reservado para um argumento type que é fornecido para criar um tipo ou método construído. Por outro lado, um argumento type é o tipo real que é substituído pelo parâmetro type 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 type em uma declaração de tipo ou método define um nome no espaço de declaração desse tipo ou método. Assim, 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 a declaração de tipo inteira, os tipos aninhados podem usar parâmetros de tipo externo. Isso também significa que os parâmetros de tipo devem sempre ser 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. Os parâmetros de tipo num tipo só podem ser referidos pelo seu nome simples; em outras palavras, eles não podem ser qualificados com o nome do tipo que contém. Embora seja um estilo de programação ruim, 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
Os 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 comparadas com o 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 resolução de sobrecarga de método.
Dentro da declaração que contém, os parâmetros de tipo são considerados tipos completos. Como um parâmetro type pode ser instanciado com muitos argumentos de tipo reais diferentes, os parâmetros type têm operações e restrições ligeiramente diferentes de outros tipos, conforme descrito abaixo:
Um parâmetro type não pode ser usado diretamente para declarar uma classe base ou interface.
As regras para pesquisa de membros em parâmetros de tipo dependem das restrições, se houver, aplicadas ao parâmetro type.
As conversões disponíveis para um parâmetro type dependem das restrições, se houver, aplicadas aos parâmetros type.
Na ausência de uma
Structurerestrição, um valor com um tipo representado por um parâmetro type pode ser comparado comNothingo usoIseIsNot.Um parâmetro type só pode ser usado em uma
Newexpressão se o parâmetro type for restringido por umaNewou umaStructurerestrição.Um parâmetro type não pode ser usado em qualquer lugar dentro de uma exceção de atributo dentro de uma
GetTypeexpressão.Os 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 type para MyStack, o mesmo argumento type 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 é vinculado a um tipo de tempo de execução que foi especificado fornecendo um argumento type para a declaração genérica. Assim, o tipo de uma variável declarada com um parâmetro type 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 que foi fornecido como o argumento type para esse parâmetro.
Restrições de tipo
Como um argumento type pode ser qualquer tipo no sistema type, um tipo ou método genérico não pode fazer suposições sobre um parâmetro type. Assim, os membros de um parâmetro de tipo são considerados os membros do tipo Object, uma vez que todos os tipos derivam de Object.
No caso de uma coleção como Stack(Of ItemType)a , este 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.
As 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 DisposableStack(Of ItemType) restringe seu parâmetro type a apenas tipos que implementam a interface System.IDisposable. Como resultado, ele pode implementar um Dispose método que descarta todos os objetos ainda deixados na fila.
Uma restrição de tipo deve ser uma das restrições Classespeciais , Structureou , ou Newdeve ser um tipo T em que:
Té uma classe, uma interface ou um parâmetro type.TnãoNotInheritableé .Tnão é um dos seguintes tipos especiais, nem é um tipo herdado de um dos seguintes tipos especiais:System.Array,System.Delegate,System.MulticastDelegate,System.Enum, ouSystem.ValueType.TnãoObjecté . Uma vez que todos os tipos derivam deObject, tal restrição não teria efeito se fosse permitida.Tdeve ser pelo menos tão acessível como o tipo ou método genérico declarado.
Várias restrições de tipo podem ser especificadas para um único parâmetro de tipo, incluindo 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 requer que o argumento type fornecido implemente uma interface genérica usando a si mesmo como um argumento type:
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.
Nota. A restrição Class de tipo especial pode ser satisfeita 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 satisfaz a Class restrição especial), e "U" uma interface que implementa (que satisfaz a 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).
Nota. As restrições de estrutura não permitem System.Nullable(Of T) , de modo que não é possível fornecer System.Nullable(Of T) como um argumento de tipo para si mesmo.
A restrição New de tipo especial requer que o argumento de tipo fornecido deve ter um construtor sem parâmetros acessível e não pode 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 deve ser esse tipo como ou herdar dele. Uma restrição de tipo de interface requer que o argumento de tipo fornecido implemente essa interface. Uma restrição de parâmetro type requer que o argumento type fornecido derive ou implemente todos os limites fornecidos para o parâmetro type 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 S type on AddRange é restrito ao parâmetro T type de List. Isso significa que um List(Of Control) restringiria AddRangeo parâmetro type a qualquer tipo que seja ou herde de Control.
Uma restrição Of S As T de parâmetro de tipo é resolvida adicionando transitivamente todas as restrições Sde T, exceto as restrições especiais (Class, StructureNew, ). É 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 por si só tem a Structure restrição. Depois de adicionar restrições, é possível que ocorram várias situações especiais:
Se existirem várias restrições de classe, a classe mais derivada é considerada a restrição. Se uma ou mais restrições de classe não tiverem nenhuma relação de herança, a restrição será insatisfatória e será um erro.
Se um parâmetro type combina uma
Structurerestrição especial com uma restrição de classe nomeada ou aClassrestrição especial, é um erro. Uma restrição de classe pode serNotInheritable, caso em que nenhum tipo derivado dessa restrição é aceito e é um erro.
O tipo pode ser um de, ou um tipo herdado de, os seguintes tipos especiais: System.Array, , , System.MulticastDelegateSystem.Enum, ou System.ValueTypeSystem.Delegate. Nesse caso, apenas 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, acabar 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 type tiver várias restrições, então o parâmetro type é considerado como tendo 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 ficarão ocultos na seguinte ordem: a restrição de classe oculta membros em restrições de interface, que ocultam membros em System.ValueType (se Structure a restrição for especificada), o que oculta 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 type deverá ser convertido para a 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 satisfazer as 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
Variância do parâmetro de tipo
Um parâmetro type em uma interface ou uma declaração de tipo delegado pode, opcionalmente, especificar um modificador de variância. Os parâmetros de tipo com modificadores de variância restringem como o parâmetro type pode ser usado na interface ou no tipo delegado, mas permitem que uma interface genérica ou um 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
As interfaces genéricas que têm parâmetros de tipo com modificadores de variância 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 de delegado é permitida).
Eles não podem conter uma classe, estrutura ou tipo enumerado aninhado.
Nota. Essas restrições são devidas ao fato de que os tipos aninhados em tipos genéricos copiam implicitamente os parâmetros genéricos de seu pai. No caso de classes aninhadas, estruturas ou tipos enumerados, esses tipos de tipos não podem ter modificadores de variância 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 type que é 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 da interface ou tipo de delegado -- e não pode ser usado em uma posição de entrada. Um tipo T é considerado válido covariante se:
Té uma classe, estrutura ou tipo enumerado.Té um tipo de delegado ou interface não genérico.Té um tipo de matriz cujo tipo de elemento é válido covariantemente.Té um parâmetro type que não foi declarado como umOutparâmetro type.Té uma interface construída ou um tipoX(Of P1,...,Pn)de delegação com argumentosA1,...,Ande tipo tais que:Se
Pifoi declarado como um parâmetro do tipo out, entãoAié válido covariantemente.Se
Pifoi declarado como um parâmetro In type, entãoAié válido contravariantemente.
O seguinte deve ser válido covariantly em uma interface ou tipo delegado:
A interface base de uma interface.
O tipo de retorno de uma função ou o tipo de delegado.
O tipo de uma propriedade se houver um
Getacessório.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
Nota.
Out não é uma palavra reservada.
Um parâmetro type que é 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 a interface ou tipo delegado -- e não pode ser usado em uma posição de saída. Um tipo T é considerado válido contravariante se:
Té uma classe, estrutura ou tipo enumerado.Té um delegado não genérico ou tipo de interface.Té um tipo de matriz cujo tipo de elemento é válido contravariantemente.Té um parâmetro type que não foi declarado como um parâmetro In type.Té uma interface construída ou um tipoX(Of P1,...,Pn)de delegação com argumentosA1,...,Ande tipo tais 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 contravariantly 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 tiver um
Setacessório.O tipo de 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 ser contravariante e covariante (como uma propriedade com um Get e acessador ou um ByRef parâmetro), um parâmetro de tipo variante Set não pode ser usado.
A covariância e a contravariância dão origem a um "problema de ambiguidade dos diamantes". 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 de IEnumerable(Of Object) duas maneiras, ambas através da conversão covariante de e através da IEnumerable(Of String) conversão covariante de IEnumerable(Of Exception). O CLR não especifica qual dos dois métodos será chamado 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, neste caso String e têm o supertipo Objectcomum), ou uma classe é declarada para implementar uma interface contravariante Exception com dois argumentos genéricos diferentes que têm um subtipo comum, então é provável que surja ambiguidade. O compilador dá um aviso sobre tais declarações.
Visual Basic language spec