Conceitos Gerais

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, Shared ou Private).

  • Modificadores de um parâmetro (por exemplo, ByVal ou ByRef).

  • 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 B deriva de um tipo A, é um erro para o tipo A derivar direta ou indiretamente do tipo B.

  • 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 é ou NotOverridable.
  • 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 Friend método deve ser substituído por um Protected método se o outro método estiver em outro assembly ao qual o método de substituição não tenha Friend acesso.
  • 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, e Optional modificadores, 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 R contém a declaração introdutória Overridable de , esta é a implementação mais derivada Mde M.

  • Caso contrário, se R contiver uma substituição de , esta é a implementação mais derivada Mdo M.

  • Caso contrário, a implementação mais derivada de M é a mesma que a da classe base direta de R.

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 Public modificador têm Public acesso. Não existem restrições à utilização de Public entidades.

  • As entidades declaradas com o Protected modificador têm Protected acesso. Protected O acesso só pode ser especificado em membros de classes (membros de tipo regular e classes aninhadas) ou em Overridable membros de módulos e estruturas padrão (que devem, por definição, ser herdados de System.Object ou System.ValueType). Um Protected membro é 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. Protected O acesso não é um superconjunto de Friend acesso.

  • As entidades declaradas com o Friend modificador têm Friend acesso. Uma entidade com Friend acesso é acessível somente dentro do programa que contém a declaração de entidade ou quaisquer assemblies que receberam Friend acesso por meio do System.Runtime.CompilerServices.InternalsVisibleToAttribute atributo.

  • As entidades declaradas com os Protected Friend modificadores têm a união de Protected e Friend acesso.

  • As entidades declaradas com o Private modificador têm Private acesso. Uma Private entidade 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 A e A.X é ilimitado.

  • O domínio de acessibilidade de A.Y, B, , B.X, B.YB.C, B.C.X, e B.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, incluindo B.C e B.D. B.D.XB.DB.Z

  • O 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:

  1. Resolver N, usando as regras para resolução de nomes qualificados ou não qualificados.

  2. Se a resolução falhar ou resolver para um parâmetro type, ocorrerá um erro em tempo de N compilação.

  3. Caso contrário, se R corresponder ao nome de um namespace em N e nenhum argumento de tipo tiver sido fornecido, ou R corresponder a um tipo acessível com N o 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.

  4. Caso contrário, se N contiver um ou mais módulos padrão e R corresponder 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. Se R corresponder 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.

  5. 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:

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

  2. Para cada tipo aninhado que contém a referência de nome, começando pelo tipo mais interno e indo para o mais externo:

    1. Se R corresponder 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.
    2. Caso contrário, se R corresponder 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.
  3. 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:

    1. Se R corresponder 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.
    2. Caso contrário, se R corresponder 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.
    3. Caso contrário, se o namespace contiver um ou mais módulos padrão acessíveis e R corresponder 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. Se R corresponder 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.
  4. Se o arquivo de origem tiver um ou mais aliases de importação e R corresponder 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.

  5. Se o ficheiro de origem que contém a referência de nome tiver uma ou mais importações:

    1. Se R corresponder 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. Se R corresponder 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.
    2. Caso contrário, se nenhuma lista de argumentos de tipo foi fornecida e R corresponde 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 e R corresponde 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.
    3. Caso contrário, se as importações contiverem um ou mais módulos padrão acessíveis e R corresponderem 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. Se R corresponder 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.
  6. Se o ambiente de compilação definir um ou mais aliases de importação e R corresponder 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.

  7. Se o ambiente de compilação definir uma ou mais importações:

    1. Se R corresponder 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. Se R corresponder 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.
    2. Caso contrário, se nenhuma lista de argumentos de tipo foi fornecida e R corresponde 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 e R corresponde ao nome de um namespace com tipos acessíveis em mais de uma importação, ocorre um erro em tempo de compilação.
    3. Caso contrário, se as importações contiverem um ou mais módulos padrão acessíveis e R corresponderem 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. Se R corresponder 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.
  8. 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 Structure restrição, um valor com um tipo representado por um parâmetro type pode ser comparado com Nothing o uso Is e IsNot.

  • Um parâmetro type só pode ser usado em uma New expressão se o parâmetro type for restringido por uma New ou uma Structure restrição.

  • Um parâmetro type não pode ser usado em qualquer lugar dentro de uma exceção de atributo dentro de uma GetType expressã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.

  • T não NotInheritableé .

  • T não é um dos seguintes tipos especiais, nem é um tipo herdado de um dos seguintes tipos especiais: System.Array, System.Delegate, System.MulticastDelegate, System.Enum, ou System.ValueType.

  • T não Objecté . Uma vez que todos os tipos derivam de Object, tal restrição não teria efeito se fosse permitida.

  • T deve 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 Structure restrição especial com uma restrição de classe nomeada ou a Class restrição especial, é um erro. Uma restrição de classe pode ser NotInheritable, 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 um Out parâmetro type.

  • T é uma interface construída ou um tipo X(Of P1,...,Pn) de delegação com argumentos A1,...,An de tipo tais que:

    • Se Pi foi declarado como um parâmetro do tipo out, então Ai é válido covariantemente.

    • Se Pi foi declarado como um parâmetro In type, então Ai é 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 Get acessório.

  • O tipo de qualquer ByRef parâ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 tipo X(Of P1,...,Pn) de delegação com argumentos A1,...,An de tipo tais que:

    • Se Pi foi declarado como um Out parâmetro de tipo, então Ai é válido contravariantemente.

    • Se Pi foi declarado como um In parâmetro de tipo, então Ai é 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 Set acessó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.