Conceptos generales

En este capítulo se tratan varios conceptos necesarios para comprender la semántica del lenguaje De Microsoft Visual Basic. Muchos de los conceptos deben estar familiarizados con los programadores de Visual Basic o los programadores de C/C++, pero sus definiciones precisas pueden diferir.

Declaraciones

Un programa de Visual Basic se compone de entidades con nombre. Estas entidades se presentan a través de declaraciones y representan el "significado" del programa.

En un nivel superior, los espacios de nombres son entidades que organizan otras entidades, como espacios de nombres y tipos anidados. Los tipos son entidades que describen valores y definen código ejecutable. Los tipos pueden contener tipos anidados y miembros de tipo. Los miembros de tipo son constantes, variables, métodos, operadores, propiedades, eventos, valores de enumeración y constructores.

Una entidad que puede contener otras entidades define un espacio de declaración. Las entidades se introducen en un espacio de declaración a través de declaraciones o herencia; el espacio de declaración contenedor se denomina contexto de declaración de las entidades. Declarar una entidad en un espacio de declaración a su vez define un nuevo espacio de declaración que puede contener declaraciones de entidad anidadas adicionales; por lo tanto, las declaraciones de un programa forman una jerarquía de espacios de declaración.

Excepto en el caso de los miembros de tipo sobrecargados, no es válido que las declaraciones introduzcan entidades con nombre idéntico del mismo tipo en el mismo contexto de declaración. Además, un espacio de declaración nunca puede contener diferentes tipos de entidades con el mismo nombre; Por ejemplo, un espacio de declaración nunca puede contener una variable y un método con el mismo nombre.

Nota. Puede ser posible en otros lenguajes crear un espacio de declaración que contenga diferentes tipos de entidades con el mismo nombre (por ejemplo, si el idioma distingue mayúsculas de minúsculas y permite declaraciones diferentes basadas en mayúsculas y minúsculas). En esa situación, la entidad más accesible se considera enlazada a ese nombre; Si más de un tipo de entidad es más accesible, el nombre es ambiguo. Public es más accesible que Protected Friend, Protected Friend es más accesible que Protected o Friend, o Protected es Friend más accesible que Private.

El espacio de declaración de un espacio de nombres es "abierto finalizado", por lo que dos declaraciones de espacio de nombres con el mismo nombre completo contribuyen al mismo espacio de declaración. En el ejemplo siguiente, las dos declaraciones de espacio de nombres contribuyen al mismo espacio de declaración, en este caso declarando dos clases con los nombres Data.Customer completos y Data.Order:

Namespace Data
    Class Customer
    End Class
End Namespace

Namespace Data
    Class Order
    End Class
End Namespace

Dado que las dos declaraciones contribuyen al mismo espacio de declaración, se produciría un error en tiempo de compilación si cada una contenía una declaración de una clase con el mismo nombre.

Sobrecarga y firmas

La única manera de declarar entidades con nombre idéntico del mismo tipo en un espacio de declaración consiste en sobrecargar. Solo se pueden sobrecargar los métodos, operadores, constructores de instancias y propiedades.

Los miembros de tipo sobrecargados deben poseer firmas únicas. La firma de un miembro de tipo consta del número de parámetros de tipo y el número y los tipos de los parámetros del miembro. Los operadores de conversión también incluyen el tipo de valor devuelto del operador en la firma.

Las siguientes no forman parte de la firma de un miembro y, por tanto, no se pueden sobrecargar en:

  • Modificadores a un miembro de tipo (por ejemplo, Shared o Private).

  • Modificadores a un parámetro (por ejemplo, ByVal o ByRef).

  • Nombres de los parámetros.

  • Tipo de valor devuelto de un método o operador (excepto los operadores de conversión) o el tipo de elemento de una propiedad.

  • Restricciones en un parámetro de tipo.

En el ejemplo siguiente se muestra un conjunto de declaraciones de método sobrecargadas junto con sus firmas. Esta declaración no sería válida, ya que varias de las declaraciones de método tienen firmas 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

Es válido definir un tipo genérico que pueda contener miembros con firmas idénticas basadas en los argumentos de tipo proporcionados. Las reglas de resolución de sobrecarga se usan para intentar desambiguar entre estas sobrecargas, aunque puede haber situaciones en las que es imposible desambiguar. Por ejemplo:

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

El ámbito del nombre de una entidad es el conjunto de todos los espacios de declaración en los que es posible hacer referencia a ese nombre sin cualificación. En general, el ámbito del nombre de una entidad es su contexto de declaración completo; sin embargo, la declaración de una entidad puede contener declaraciones anidadas de entidades con el mismo nombre. En ese caso, la entidad anidada sombrea u oculta, la entidad externa y el acceso a la entidad sombreada solo es posible mediante la calificación.

El sombreado a través del anidamiento se produce en espacios de nombres o tipos anidados en espacios de nombres, en tipos anidados dentro de otros tipos y en los cuerpos de los miembros de tipo. El sombreado a través del anidamiento de declaraciones siempre se produce implícitamente; no se requiere ninguna sintaxis explícita.

En el ejemplo siguiente, dentro del F método , la variable i de instancia está sombreada por la variable ilocal , pero dentro del G método , i todavía hace referencia a la variable de instancia.

Class Test
    Private i As Integer = 0

    Sub F()
        Dim i As Integer = 1
    End Sub

    Sub G()
        i = 1
    End Sub
End Class

Cuando un nombre en un ámbito interno oculta un nombre en un ámbito externo, sombrea todas las apariciones sobrecargadas de ese nombre. En el ejemplo siguiente, la llamada F(1) invoca al F declarado en Inner porque todas las apariciones externas de F están ocultas por la declaración interna. Por el mismo motivo, la llamada F("Hello") está en error.

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

Herencia

Una relación de herencia es una en la que un tipo (el tipo derivado ) se deriva de otro (el tipo base ), de modo que el espacio de declaración del tipo derivado contiene implícitamente los miembros de tipo no constructor accesibles y los tipos anidados de su tipo base. En el ejemplo siguiente, la clase A es la clase base de By B se deriva de A.

Class A
End Class

Class B
    Inherits A
End Class

Puesto A que no especifica explícitamente una clase base, su clase base es implícitamente Object.

A continuación se muestran aspectos importantes de la herencia:

  • La herencia es transitiva. Si el tipo C se deriva del tipo B y el tipo B se deriva del tipo A, el tipo C hereda los miembros de tipo declarados en el tipo B , así como los miembros de tipo declarados en el tipo A.

  • Un tipo derivado amplía, pero no puede restringirse, su tipo base. Un tipo derivado puede agregar nuevos miembros de tipo y puede sombrear miembros de tipo heredados, pero no puede quitar la definición de un miembro de tipo heredado.

  • Dado que una instancia de un tipo contiene todos los miembros de tipo de su tipo base, siempre existe una conversión de un tipo derivado a su tipo base.

  • Todos los tipos deben tener un tipo base, excepto para el tipo Object. Por lo tanto, Object es el tipo base final de todos los tipos, y todos los tipos se pueden convertir en él.

  • No se permite la circularidad en la derivación. Es decir, cuando un tipo B se deriva de un tipo A, es un error para que el tipo A derive directamente o indirectamente del tipo B.

  • Un tipo no puede derivar directa o indirectamente de un tipo anidado dentro de él.

En el ejemplo siguiente se produce un error en tiempo de compilación porque las clases dependen circularmente entre sí.

Class A
    Inherits B
End Class

Class B
    Inherits C
End Class

Class C
    Inherits A
End Class

En el ejemplo siguiente también se genera un error en tiempo de compilación porque B deriva indirectamente de su clase C anidada a través de la clase A.

Class A
    Inherits B.C
End Class

Class B
    Inherits A

    Public Class C
    End Class 
End Class

En el ejemplo siguiente no se produce un error porque la clase A no deriva de la clase B.

Class A
    Class B
        Inherits A
    End Class 
End Class

Clases MustInherit y NotInheritable

Una MustInherit clase es un tipo incompleto que solo puede actuar como un tipo base. No se puede crear una instancia de una MustInherit clase, por lo que es un error usar el New operador en uno. Es válido declarar variables de MustInherit clases; estas variables solo se pueden asignar Nothing o un valor que sea de una clase derivada de la MustInherit clase .

Cuando una clase normal se deriva de una MustInherit clase, la clase normal debe invalidar todos los miembros heredados MustOverride . Por ejemplo:

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

La MustInherit clase A presenta un MustOverride método F. La clase B presenta un método Gadicional, pero no proporciona una implementación de F. Por lo tanto, la clase B también debe declararse MustInherit. La clase C invalida F y proporciona una implementación real. Puesto que no hay miembros pendientes MustOverride en la clase C, no es necesario que sea MustInherit.

Una NotInheritable clase es una clase desde la que no se puede derivar otra clase. NotInheritable Las clases se usan principalmente para evitar la derivación no deseada.

En este ejemplo, la clase B está en error porque intenta derivar de la NotInheritable clase A. Una clase no se puede marcar como y MustInheritNotInheritable.

NotInheritable Class A
End Class

Class B
    ' Error, a class cannot derive from a NotInheritable class.
    Inherits A
End Class

Interfaces y herencia múltiple

A diferencia de otros tipos, que solo derivan de un solo tipo base, una interfaz puede derivar de varias interfaces base. Debido a esto, una interfaz puede heredar un miembro de tipo con nombre idéntico de diferentes interfaces base. En tal caso, el nombre heredado de multiplicación no está disponible en la interfaz derivada y hacer referencia a cualquiera de esos miembros de tipo a través de la interfaz derivada produce un error en tiempo de compilación, independientemente de las firmas o sobrecargas. En su lugar, se debe hacer referencia a los miembros de tipo en conflicto a través de un nombre de interfaz base.

En el ejemplo siguiente, las dos primeras instrucciones provocan errores en tiempo de compilación porque el miembro Count heredado de multiplicación no está disponible en la interfaz 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 se muestra en el ejemplo, la ambigüedad se resuelve mediante la conversión de x al tipo de interfaz base adecuado. Estas conversiones no tienen costos en tiempo de ejecución; simplemente constan de ver la instancia como un tipo menos derivado en tiempo de compilación.

Cuando un único miembro de tipo se hereda de la misma interfaz base a través de varias rutas de acceso, el miembro de tipo se trata como si solo se heredase una vez. En otras palabras, la interfaz derivada solo contiene una instancia de cada miembro de tipo heredada de una interfaz base determinada. Por ejemplo:

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

Si se sombrea un nombre de miembro de tipo en una ruta de acceso a través de la jerarquía de herencia, el nombre se sombrea en todas las rutas de acceso. En el ejemplo siguiente, el IBase.F miembro sombrea el ILeft.F miembro, pero no está sombreado en 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

La invocación d.F(1) selecciona ILeft.F, aunque IBase.F parece no estar sombreada en la ruta de acceso que conduce a través IRightde . Dado que la ruta de acceso de IDerived a las IBaseILeft sombras IBase.F, el miembro también se sombrea en la ruta de acceso de IDerived a .IBaseIRight

Sombreado

Un tipo derivado sombrea el nombre de un miembro de tipo heredado al volver a declararlo. Si se sombrea un nombre no se quitan los miembros de tipo heredados con ese nombre; simplemente hace que todos los miembros de tipo heredados con ese nombre no estén disponibles en la clase derivada. La declaración de sombreado puede ser cualquier tipo de entidad.

Las entidades de las que se pueden sobrecargar pueden elegir una de las dos formas de sombreado. El sombreado por nombre se especifica mediante la Shadows palabra clave . Una entidad que sombrea por nombre oculta todo por ese nombre en la clase base, incluidas todas las sobrecargas. La sombra por nombre y firma se especifica mediante la Overloads palabra clave . Una entidad que sombrea por nombre y firma oculta todo por ese nombre con la misma firma que la entidad. Por ejemplo:

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

Si se sombrea un método con un ParamArray argumento por nombre y firma, solo se oculta la firma individual, no todas las firmas expandidas posibles. Esto es cierto incluso si la firma del método de sombreado coincide con la firma no expandida del método sombreado. En el ejemplo siguiente:

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 , aunque Derived.F tenga la misma firma que la forma no expandida de Base.F.

Por el contrario, un método con un ParamArray argumento solo sombrea métodos con la misma firma, no todas las firmas expandidas posibles. En el ejemplo siguiente:

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 , aunque Derived.F tenga un formulario expandido que tenga la misma firma Base.Fque .

Método o propiedad de sombreado que no especifica Shadows o Overloads asume Overloads si el método o la propiedad se declaran Overrides, Shadows de lo contrario. Si un miembro de un conjunto de entidades sobrecargadas especifica la Shadows palabra clave o Overloads , todas deben especificarla. Las Shadows palabras clave y Overloads no se pueden especificar al mismo tiempo. Ni ni ShadowsOverloads se puede especificar en un módulo estándar; los miembros de un módulo estándar sombreanan implícitamente de Object.

Es válido sombrear el nombre de un miembro de tipo que se ha multiplicado a través de la herencia de interfaz (y, por tanto, no está disponible), lo que hace que el nombre esté disponible en la interfaz derivada.

Por ejemplo:

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

Dado que los métodos pueden sombrear métodos heredados, es posible que una clase contenga varios Overridable métodos con la misma firma. Esto no presenta un problema de ambigüedad, ya que solo el método más derivado es visible. En el ejemplo siguiente, las C clases y D contienen dos Overridable métodos con la misma firma:

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

Hay dos Overridable métodos aquí: uno introducido por la clase A y el introducido por la clase C. El método introducido por la clase C oculta el método heredado de la clase A. Por lo tanto, la Overrides declaración de la clase D invalida el método introducido por la clase Cy no es posible que la clase D invalide el método introducido por la clase A. En el ejemplo se genera la salida:

B.F
B.F
D.F
D.F

Es posible invocar el método oculto Overridable accediendo a una instancia de clase D a través de un tipo menos derivado en el que el método no está oculto.

No es válido sombrear un MustOverride método, ya que, en la mayoría de los casos, esto haría que la clase no se pueda usar. Por ejemplo:

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

En este caso, la clase MoreDerived es necesaria para invalidar el MustOverride método Base.F, pero porque la clase Derived sombrea Base.F, esto no es posible. No hay ninguna manera de declarar un descendiente válido de Derived.

A diferencia de sombrear un nombre desde un ámbito externo, el sombreado de un nombre accesible desde un ámbito heredado hace que se notifique una advertencia, como en el ejemplo siguiente:

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

La declaración del método F en la clase Derived hace que se notifique una advertencia. La sombreación de un nombre heredado no es específicamente un error, ya que esto impediría la evolución independiente de las clases base. Por ejemplo, la situación anterior podría haberse producido porque una versión posterior de la clase Base introdujo un método F que no estaba presente en una versión anterior de la clase . Si la situación anterior era un error, cualquier cambio realizado en una clase base en una biblioteca de clases con versiones por separado podría hacer que las clases derivadas no sean válidas.

La advertencia causada por la sombra de un nombre heredado se puede eliminar mediante el Shadows uso del modificador o Overloads :

Class Base
    Public Sub F()
    End Sub 
End Class 

Class Derived
    Inherits Base

    Public Shadows Sub F() 'OK.
    End Sub
End Class

El Shadows modificador indica la intención de sombrear el miembro heredado. No es un error especificar el Shadows modificador o Overloads si no hay ningún nombre de miembro de tipo en shadow.

Una declaración de un nuevo miembro sombrea un miembro heredado solo dentro del ámbito del nuevo miembro, como en el ejemplo siguiente:

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

En el ejemplo anterior, la declaración de método F en la clase Derived sombrea el método F que se heredó de la clase Base, pero dado que el nuevo método F de la clase Derived tiene Private acceso, su ámbito no se extiende a la clase MoreDerived. Por lo tanto, la llamada F() en MoreDerived.G es válida e invocará Base.F. En el caso de los miembros de tipo sobrecargados, todo el conjunto de miembros de tipo sobrecargado se trata como si todos tuvieran el acceso más permisivo para los fines de sombreado.

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

En este ejemplo, aunque la declaración de F() en Derived se declara con Private acceso, la sobrecarga se F(Integer) declara con Public acceso. Por lo tanto, con el fin de sombrear, el nombre F de Derived se trata como si fuera Public, por lo que ambos métodos sombrea F en Base.

Implementación

Existe una relación de implementación cuando un tipo declara que implementa una interfaz y el tipo implementa todos los miembros de tipo de la interfaz. Un tipo que implementa una interfaz determinada se puede convertir a esa interfaz. No se pueden crear instancias de interfaces, pero es válido declarar variables de interfaces; estas variables solo se pueden asignar a un valor que sea de una clase que implemente la interfaz. Por ejemplo:

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

Un tipo que implementa una interfaz con miembros de tipo heredados multiplicados debe seguir implementando esos métodos, aunque no se pueda tener acceso directamente desde la interfaz derivada que se está implementando. Por ejemplo:

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

Incluso MustInherit las clases deben proporcionar implementaciones de todos los miembros de las interfaces implementadas; sin embargo, pueden aplazar la implementación de estos métodos declarando como MustOverride. Por ejemplo:

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

Un tipo puede optar por volver a implementar una interfaz que implementa su tipo base. Para volver a implementar la interfaz, el tipo debe indicar explícitamente que implementa la interfaz. Un tipo que vuelva a implementar una interfaz puede optar por volver a implementar solo algunos, pero no todos, de los miembros de la interfaz; los miembros que no se vuelvan a implementar seguirán usando la implementación del tipo base. Por ejemplo:

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

En este ejemplo se imprime:

TestDerived.DerivedTest1
TestBase.Test2

Cuando un tipo derivado implementa una interfaz cuyas interfaces base se implementan mediante los tipos base del tipo derivado, el tipo derivado solo puede optar por implementar los miembros de tipo de la interfaz que los tipos base aún no implementan. Por ejemplo:

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

Un método de interfaz también se puede implementar mediante un método reemplazable en un tipo base. En ese caso, un tipo derivado también puede invalidar el método reemplazable y modificar la implementación de la interfaz. Por ejemplo:

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

Implementación de métodos

Un tipo implementa un miembro de tipo de una interfaz implementada proporcionando un método con una Implements cláusula . Los dos miembros de tipo deben tener el mismo número de parámetros, todos los tipos y modificadores de los parámetros deben coincidir, incluido el valor predeterminado de los parámetros opcionales, el tipo de valor devuelto debe coincidir y todas las restricciones en los parámetros del método deben coincidir. Por ejemplo:

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

Un único método puede implementar cualquier número de miembros de tipo de interfaz si todos cumplen los criterios anteriores. Por ejemplo:

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

Al implementar un método en una interfaz genérica, el método de implementación debe proporcionar los argumentos de tipo que corresponden a los parámetros de tipo de la interfaz. Por ejemplo:

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

Tenga en cuenta que es posible que una interfaz genérica no se pueda implementar para algún 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

El polimorfismo proporciona la capacidad de variar la implementación de un método o propiedad. Con el polimorfismo, el mismo método o propiedad puede realizar diferentes acciones en función del tipo en tiempo de ejecución de la instancia que lo invoque. Los métodos o propiedades que son polimórficos se denominan invalidables. Por el contrario, la implementación de un método o propiedad no reemplazable es invariable; la implementación es la misma si se invoca el método o la propiedad en una instancia de la clase en la que se declara o una instancia de una clase derivada. Cuando se invoca un método o propiedad no reemplazable, el tipo en tiempo de compilación de la instancia es el factor determinante. Por ejemplo:

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

Un método invalidable también puede ser MustOverride, lo que significa que no proporciona ningún cuerpo del método y se debe invalidar. MustOverride los métodos solo se permiten en MustInherit clases.

En el ejemplo siguiente, la clase Shape define la noción abstracta de un objeto de forma geométrica que puede pintarse a sí mismo:

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

El Paint método se debe MustOverride a que no hay ninguna implementación predeterminada significativa. Las clases Ellipse y Box son implementaciones concretas de Shape. Dado que estas clases no MustInheritson , son necesarias para invalidar el Paint método y proporcionar una implementación real.

Se trata de un error para que un acceso base haga referencia a un MustOverride método, como se muestra en el ejemplo siguiente:

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

Se notifica un error para la MyBase.F() invocación porque hace referencia a un MustOverride método .

Invalidar métodos

Un tipo puede invalidar un método reemplazable heredado declarando un método con el mismo nombre y , firma y marcando la declaración con el Overrides modificador . Hay requisitos adicionales sobre la invalidación de métodos, que se enumeran a continuación. Mientras que una Overridable declaración de método introduce un nuevo método, una Overrides declaración de método reemplaza la implementación heredada del método.

Se puede declarar NotOverridableun método de invalidación , lo que impide que se invalide aún más el método en tipos derivados. En efecto, NotOverridable los métodos se convierten en no invalidables en cualquier clase derivada adicional.

Considere el ejemplo siguiente:

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

En el ejemplo, la clase B proporciona dos Overrides métodos: un método F que tiene el NotOverridable modificador y un método G que no lo hace. El uso del modificador impide que la NotOverridable clase C invalide aún más el método F.

También se puede declarar MustOverrideun método de invalidación, incluso si el método que está invalidando no se declara MustOverride. Esto requiere que se declare la clase contenedora MustInherit y que cualquier otra clase derivada que no se declare MustInherit debe invalidar el método . Por ejemplo:

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

En el ejemplo, la clase B invalida A.F con un MustOverride método . Esto significa que cualquier clase derivada de B tendrá que invalidar F, a menos que también se declaren MustInherit .

Se produce un error en tiempo de compilación a menos que todos los siguientes sean true para un método de invalidación:

  • El contexto de declaración contiene un único método heredado accesible con la misma firma y tipo de valor devuelto (si existe) que el método de invalidación.
  • El método heredado que se está reemplazando se puede invalidar. En otras palabras, el método heredado que se reemplaza no Shared es o NotOverridable.
  • El dominio de accesibilidad del método que se declara es el mismo que el dominio de accesibilidad del método heredado que se invalida. Hay una excepción: un Protected Friend método debe invalidarse mediante un Protected método si el otro método está en otro ensamblado al que el método de invalidación no tiene Friend acceso.
  • Los parámetros del método de invalidación coinciden con los parámetros del método invalidado en lo que respecta al uso de los ByValmodificadores , ByRefParamArray, y Optional , incluidos los valores proporcionados para los parámetros opcionales.
  • Los parámetros de tipo del método de invalidación coinciden con los parámetros de tipo del método invalidado en lo que respecta a las restricciones de tipo.

Al invalidar un método en un tipo genérico base, el método de invalidación debe proporcionar los argumentos de tipo que corresponden a los parámetros de tipo base. Por ejemplo:

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

Tenga en cuenta que es posible que un método reemplazable en una clase genérica no se pueda invalidar para algunos conjuntos de argumentos de tipo. Si el método se declara MustOverride, esto significa que es posible que algunas cadenas de herencia no sean posibles. Por ejemplo:

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

Una declaración de invalidación puede tener acceso al método base invalidado mediante un acceso base, como en el ejemplo siguiente:

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

En el ejemplo, la invocación de MyBase.PrintVariables() en la clase Derived invoca el PrintVariables método declarado en la clase Base. Un acceso base deshabilita el mecanismo de invocación reemplazable y simplemente trata el método base como un método no invalidable. Si se hubiera escrito la invocación Derived , invocaría recursivamente el PrintVariables método declarado en Derived, no el declarado en Base.CType(Me, Base).PrintVariables()

Solo cuando incluye un Overrides modificador puede invalidar otro método. En todos los demás casos, un método con la misma firma que un método heredado simplemente sombrea el método heredado, como en el ejemplo siguiente:

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

En el ejemplo, el método F de la clase Derived no incluye un Overrides modificador y, por tanto, no invalida el método F en la clase Base. En su lugar, el método F de la clase Derived sombrea el método de la clase Basey se notifica una advertencia porque la declaración no incluye un Shadows modificador o Overloads .

En el ejemplo siguiente, el método F de la clase Derived sombrea el método F reemplazable heredado de la clase 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

Puesto que el nuevo método F de la clase Derived tiene Private acceso, su ámbito solo incluye el cuerpo de clase de Derived y no se extiende a la clase MoreDerived. Por lo tanto, se permite que la declaración del método F en la clase MoreDerived invalide el método F heredado de la clase Base.

Cuando se invoca un Overridable método, se llama a la implementación más derivada del método de instancia, en función del tipo de la instancia, independientemente de si la llamada es al método de la clase base o a la clase derivada. La implementación más derivada de un Overridable método M con respecto a una clase R se determina de la siguiente manera:

  • Si R contiene la declaración de introducción Overridable de M, se trata de la implementación más derivada de M.

  • De lo contrario, si R contiene una invalidación de M, se trata de la implementación más derivada de M.

  • De lo contrario, la implementación más derivada de M es la misma que la de la clase base directa de R.

Accesibilidad

Una declaración especifica la accesibilidad de la entidad que declara. La accesibilidad de una entidad no cambia el ámbito del nombre de una entidad. El dominio de accesibilidad de una declaración es el conjunto de todos los espacios de declaración en los que se puede acceder a la entidad declarada.

Los cinco tipos de acceso son Public, Protected, Friend, Protected Friendy Private. Public es el tipo de acceso más permisivo y los otros cuatro tipos son todos subconjuntos de Public. El tipo de acceso menos permisivo es Privatey los otros cuatro tipos de acceso son todos superconjuntos de Private.

AccessModifier
    : 'Public'
    | 'Protected'
    | 'Friend'
    | 'Private'
    | 'Protected' 'Friend'
    ;

El tipo de acceso de una declaración se especifica mediante un modificador de acceso opcional, que puede ser , , , Privateo la combinación de Protected y Friend. FriendProtectedPublic Si no se especifica ningún modificador de acceso, el tipo de acceso predeterminado depende del contexto de declaración; los tipos de acceso permitidos también dependen del contexto de declaración.

  • Las entidades declaradas con el Public modificador tienen Public acceso. No hay restricciones en el uso de Public entidades.

  • Las entidades declaradas con el Protected modificador tienen Protected acceso. Protected el acceso solo se puede especificar en miembros de clases (miembros de tipo normal y clases anidadas) o en Overridable miembros de módulos y estructuras estándar (que deben heredar, por definición, de System.Object o System.ValueType). Un Protected miembro es accesible para una clase derivada, siempre que el miembro no sea un miembro de instancia o que el acceso tenga lugar a través de una instancia de la clase derivada. Protected access no es un superconjunto de Friend acceso.

  • Las entidades declaradas con el Friend modificador tienen Friend acceso. Solo se puede acceder a una entidad con Friend acceso dentro del programa que contiene la declaración de entidad o los ensamblados a los que se les ha concedido Friend acceso a través del System.Runtime.CompilerServices.InternalsVisibleToAttribute atributo .

  • Las entidades declaradas con los Protected Friend modificadores tienen la unión de Protected y Friend el acceso.

  • Las entidades declaradas con el Private modificador tienen Private acceso. Solo se puede acceder a una Private entidad dentro de su contexto de declaración, incluidas las entidades anidadas.

La accesibilidad de una declaración no depende de la accesibilidad del contexto de declaración. Por ejemplo, un tipo declarado con Private acceso puede contener un miembro de tipo con Public acceso.

En el código siguiente se muestran varios dominios de accesibilidad:

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

Las clases y los miembros de este ejemplo tienen los siguientes dominios de accesibilidad:

  • El dominio de accesibilidad de A y A.X es ilimitado.

  • El dominio de accesibilidad de A.Y, , B.XB, B.YB.C, , B.C.X, y B.C.Y es el programa contenedor.

  • El dominio de accesibilidad de A.Z es A.

  • El dominio de accesibilidad de B.Z, B.D, B.D.X, y B.D.Y es B, incluido B.C y B.D.

  • El dominio de accesibilidad de B.C.Z es B.C.

  • El dominio de accesibilidad de B.D.Z es B.D.

Como se muestra en el ejemplo, el dominio de accesibilidad de un miembro nunca es mayor que el de un tipo contenedor. Por ejemplo, aunque todos los X miembros hayan Public declarado accesibilidad, todos pero A.X tienen dominios de accesibilidad restringidos por un tipo contenedor.

El acceso a Protected los miembros de instancia debe ser a través de una instancia del tipo derivado para que los tipos no relacionados no puedan obtener acceso a los miembros protegidos de los demás. Por ejemplo:

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

En el ejemplo anterior, la clase Guest solo tiene acceso al campo protegido Password si está calificado con una instancia de Guest. Esto impide Guest obtener acceso al Password campo de un Employee objeto simplemente mediante la conversión a User.

Para los fines del acceso de Protected miembros en tipos genéricos, el contexto de declaración incluye parámetros de tipo. Esto significa que un tipo derivado con un conjunto de argumentos de tipo no tiene acceso a los Protected miembros de un tipo derivado con un conjunto diferente de argumentos de tipo. Por ejemplo:

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. El lenguaje C# (y posiblemente otros lenguajes) permite que un tipo genérico acceda Protected a miembros independientemente de qué argumentos de tipo se proporcionen. Esto debe tenerse en cuenta al diseñar clases genéricas que contengan Protected miembros.

Tipos constituyentes

Los tipos constituyentes de una declaración son los tipos a los que hace referencia la declaración. Por ejemplo, el tipo de una constante, el tipo de valor devuelto de un método y los tipos de parámetro de un constructor son todos los tipos constituyentes. El dominio de accesibilidad de un tipo constituyente de una declaración debe ser el mismo que o un superconjunto del dominio de accesibilidad de la propia declaración. Por ejemplo:

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

Nombres de tipo y espacio de nombres

Muchas construcciones de lenguaje requieren que se especifique un espacio de nombres o un tipo; se pueden especificar mediante un formato completo del espacio de nombres o el nombre del tipo. Un nombre completo consta de una serie de identificadores separados por puntos; el identificador del lado derecho de un período se resuelve en el espacio de declaración especificado por el identificador del lado izquierdo del período.

El nombre completo de un espacio de nombres o un tipo es un nombre completo que contiene el nombre de todos los espacios de nombres y tipos que contienen. En otras palabras, el nombre completo de un espacio de nombres o tipo es N.T, donde T es el nombre de la entidad y N es el nombre completo de su entidad contenedora.

En el ejemplo siguiente se muestran varias declaraciones de espacio de nombres y tipos junto con sus nombres completos asociados en los comentarios en línea.

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 el espacio de nombres X.Y se ha declarado en dos ubicaciones diferentes en el código fuente, pero estas dos declaraciones parciales constituyen solo un único espacio de nombres denominado X.Y que contiene la clase D y la clase E.

En algunas situaciones, un nombre completo puede comenzar con la palabra clave Global. La palabra clave representa el espacio de nombres más externo sin nombre, que es útil en situaciones en las que una declaración sombrea un espacio de nombres envolvente. La Global palabra clave permite "escapar" al espacio de nombres más externo en esa situación. Por ejemplo:

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

En el ejemplo anterior, la primera llamada al método no es válida porque el identificador System se enlaza a la clase System, no al espacio de nombres System. La única manera de acceder al System espacio de nombres es usar Global para escapar al espacio de nombres más externo. Global no se puede usar en una Imports instrucción o Namespace declaración.

Dado que otros lenguajes pueden introducir tipos y espacios de nombres que coinciden con palabras clave en el lenguaje, Visual Basic reconoce las palabras clave para formar parte de un nombre completo siempre que sigan un período. Las palabras clave usadas de esta manera se tratan como identificadores. Por ejemplo, el identificador X.Default.Class calificado es un identificador calificado válido, mientras Default.Class que no lo es.

Resolución de nombres calificada para espacios de nombres y tipos

Dado un espacio de nombres completo o un nombre de tipo del formulario N.R(Of A), donde R es el identificador más a la derecha en el nombre completo y A es una lista de argumentos de tipo opcional, los pasos siguientes describen cómo determinar a qué espacio de nombres o tipo hace referencia el nombre completo:

  1. Resuelva Ny use las reglas para la resolución de nombres calificada o no calificada.

  2. Si se produce un error en la resolución de o se resuelve en un parámetro de tipo, se produce un error en tiempo de N compilación.

  3. De lo contrario, si R coincide con el nombre de un espacio de nombres en N y no se proporcionó ningún argumento de tipo, o R coincide con un tipo accesible en N con el mismo número de parámetros de tipo que argumentos de tipo, si existe, el nombre completo hace referencia a ese espacio de nombres o tipo.

  4. De lo contrario, si N contiene uno o varios módulos estándar, y R coincide con el nombre de un tipo accesible con el mismo número de parámetros de tipo como argumentos de tipo, si existe, en exactamente un módulo estándar, el nombre completo hace referencia a ese tipo. Si R coincide con el nombre de los tipos accesibles con el mismo número de parámetros de tipo que los argumentos de tipo, si los hay, en más de un módulo estándar, se produce un error en tiempo de compilación.

  5. De lo contrario, se produce un error en tiempo de compilación.

Nota. Una implicación de este proceso de resolución es que los miembros de tipo no sombrea espacios de nombres ni tipos al resolver nombres de tipo o espacio de nombres.

Resolución de nombres no calificada para espacios de nombres y tipos

Dado un nombre R(Of A)sin calificar , donde A es una lista de argumentos de tipo opcional, los pasos siguientes describen cómo determinar a qué espacio de nombres o escribe el nombre no completo hace referencia:

  1. Si R coincide con el nombre de un parámetro de tipo del método actual y no se proporcionó ningún argumento de tipo, el nombre no calificado hace referencia a ese parámetro de tipo.

  2. Para cada tipo anidado que contenga la referencia de nombre, empezando por el tipo más interno y pasando al exterior más externo:

    1. Si R coincide con el nombre de un parámetro de tipo en el tipo actual y no se proporcionó ningún argumento de tipo, el nombre no calificado hace referencia a ese parámetro de tipo.
    2. De lo contrario, si R coincide con el nombre de un tipo anidado accesible con el mismo número de parámetros de tipo que argumentos de tipo, si existe, el nombre no calificado hace referencia a ese tipo.
  3. Para cada espacio de nombres anidado que contenga la referencia de nombre, empezando por el espacio de nombres más interno y yendo al espacio de nombres más externo:

    1. Si R coincide con el nombre de un espacio de nombres anidado en el espacio de nombres actual y no se proporciona ninguna lista de argumentos de tipo, el nombre no completo hace referencia a ese espacio de nombres anidado.
    2. De lo contrario, si R coincide con el nombre de un tipo accesible con el mismo número de parámetros de tipo que argumentos de tipo, si existe, en el espacio de nombres actual, el nombre no completo hace referencia a ese tipo.
    3. De lo contrario, si el espacio de nombres contiene uno o varios módulos estándar accesibles y R coincide con el nombre de un tipo anidado accesible con el mismo número de parámetros de tipo que los argumentos de tipo, si los hay, en exactamente un módulo estándar, el nombre no completo hace referencia a ese tipo anidado. Si R coincide con el nombre de los tipos anidados accesibles con el mismo número de parámetros de tipo que los argumentos de tipo, si los hay, en más de un módulo estándar, se produce un error en tiempo de compilación.
  4. Si el archivo de origen tiene uno o varios alias de importación y R coincide con el nombre de uno de ellos, el nombre no completo hace referencia a ese alias de importación. Si se proporciona una lista de argumentos de tipo, se produce un error en tiempo de compilación.

  5. Si el archivo de origen que contiene la referencia de nombre tiene una o varias importaciones:

    1. Si R coincide con el nombre de un tipo accesible con el mismo número de parámetros de tipo que los argumentos de tipo, si los hay, en exactamente una importación, el nombre no calificado hace referencia a ese tipo. Si R coincide con el nombre de un tipo accesible con el mismo número de parámetros de tipo que los argumentos de tipo, si los hay, en más de una importación y todos no son del mismo tipo, se produce un error en tiempo de compilación.
    2. De lo contrario, si no se proporcionó ninguna lista de argumentos de tipo y R coincide con el nombre de un espacio de nombres con tipos accesibles en exactamente una importación, el nombre no completo hace referencia a ese espacio de nombres. Si no se proporcionó ninguna lista de argumentos de tipo y R coincide con el nombre de un espacio de nombres con tipos accesibles en más de una importación y todos no son el mismo espacio de nombres, se produce un error en tiempo de compilación.
    3. De lo contrario, si las importaciones contienen uno o varios módulos estándar accesibles, y R coincide con el nombre de un tipo anidado accesible con el mismo número de parámetros de tipo que los argumentos de tipo, si los hay, en exactamente un módulo estándar, el nombre no calificado hace referencia a ese tipo. Si R coincide con el nombre de los tipos anidados accesibles con el mismo número de parámetros de tipo que los argumentos de tipo, si los hay, en más de un módulo estándar, se produce un error en tiempo de compilación.
  6. Si el entorno de compilación define uno o varios alias de importación y R coincide con el nombre de uno de ellos, el nombre no completo hace referencia a ese alias de importación. Si se proporciona una lista de argumentos de tipo, se produce un error en tiempo de compilación.

  7. Si el entorno de compilación define una o varias importaciones:

    1. Si R coincide con el nombre de un tipo accesible con el mismo número de parámetros de tipo que los argumentos de tipo, si los hay, en exactamente una importación, el nombre no calificado hace referencia a ese tipo. Si R coincide con el nombre de un tipo accesible con el mismo número de parámetros de tipo que los argumentos de tipo, si los hay, en más de una importación, se produce un error en tiempo de compilación.
    2. De lo contrario, si no se proporcionó ninguna lista de argumentos de tipo y R coincide con el nombre de un espacio de nombres con tipos accesibles en exactamente una importación, el nombre no completo hace referencia a ese espacio de nombres. Si no se proporcionó ninguna lista de argumentos de tipo y R coincide con el nombre de un espacio de nombres con tipos accesibles en más de una importación, se produce un error en tiempo de compilación.
    3. De lo contrario, si las importaciones contienen uno o varios módulos estándar accesibles, y R coincide con el nombre de un tipo anidado accesible con el mismo número de parámetros de tipo que los argumentos de tipo, si los hay, en exactamente un módulo estándar, el nombre no calificado hace referencia a ese tipo. Si R coincide con el nombre de los tipos anidados accesibles con el mismo número de parámetros de tipo que los argumentos de tipo, si los hay, en más de un módulo estándar, se produce un error en tiempo de compilación.
  8. De lo contrario, se produce un error en tiempo de compilación.

Nota. Una implicación de este proceso de resolución es que los miembros de tipo no sombrea espacios de nombres ni tipos al resolver nombres de tipo o espacio de nombres.

Normalmente, un nombre solo puede producirse una vez en un espacio de nombres determinado. Sin embargo, dado que los espacios de nombres se pueden declarar en varios ensamblados de .NET, es posible tener una situación en la que dos ensamblados definen un tipo con el mismo nombre completo. En ese caso, se prefiere un tipo declarado en el conjunto actual de archivos de origen sobre un tipo declarado en un ensamblado .NET externo. De lo contrario, el nombre es ambiguo y no hay ninguna manera de desambiguar el nombre.

variables

Una variable representa una ubicación de almacenamiento. Cada variable tiene un tipo que determina qué valores se pueden almacenar en ella. Dado que Visual Basic es un lenguaje seguro para tipos, cada variable de un programa tiene un tipo y el lenguaje garantiza que los valores almacenados en variables sean siempre del tipo adecuado. Las variables siempre se inicializan con el valor predeterminado de su tipo antes de que se pueda realizar cualquier referencia a la variable. No es posible acceder a la memoria sin inicializar.

Tipos y métodos genéricos

Los tipos (excepto los módulos estándar y los tipos enumerados) y los métodos pueden declarar parámetros de tipo, que son tipos que no se proporcionarán hasta que se declare una instancia del tipo o se invoque el método. Los tipos y métodos con parámetros de tipo también se conocen como tipos genéricos y métodos genéricos, respectivamente, porque el tipo o método se debe escribir genéricamente, sin conocimiento específico de los tipos que se proporcionarán mediante código que use el tipo o método.

Nota. En este momento, aunque los métodos y delegados pueden ser genéricos, propiedades, eventos y operadores no pueden ser genéricos. Sin embargo, pueden usar parámetros de tipo de la clase contenedora.

Desde la perspectiva del tipo o método genéricos, un parámetro de tipo es un tipo de marcador de posición que se rellenará con un tipo real cuando se use el tipo o el método. Los argumentos de tipo se sustituyen por los parámetros de tipo en el tipo o método en el punto en el que se usa el tipo o el método. Por ejemplo, una clase de pila genérica podría implementarse 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

Las declaraciones que usan la Stack(Of ItemType) clase deben proporcionar un argumento de tipo para el parámetro ItemTypede tipo . A continuación, este tipo se rellena dondequiera que ItemType se use dentro de la clase :

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

Los parámetros de tipo se pueden proporcionar en declaraciones de tipo o método. Cada parámetro de tipo es un identificador que es un marcador de posición para un argumento de tipo que se proporciona para crear un tipo o método construido. Por el contrario, un argumento de tipo es el tipo real que se sustituye por el parámetro de tipo cuando se usa un tipo o método genéricos.

TypeParameterList
    : OpenParenthesis 'Of' TypeParameter ( Comma TypeParameter )* CloseParenthesis
    ;

TypeParameter
    : VarianceModifier? Identifier TypeParameterConstraints?
    ;

VarianceModifier
    : 'In' | 'Out'
    ;

Cada parámetro de tipo de una declaración de tipo o método define un nombre en el espacio de declaración de ese tipo o método. Por lo tanto, no puede tener el mismo nombre que otro parámetro de tipo, un miembro de tipo, un parámetro de método o una variable local. El ámbito de un parámetro de tipo en un tipo o método es todo el tipo o método. Dado que los parámetros de tipo se limitan a toda la declaración de tipo, los tipos anidados pueden usar parámetros de tipo externo. Esto también significa que los parámetros de tipo siempre deben especificarse al acceder a tipos anidados 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

A diferencia de otros miembros de una clase, los parámetros de tipo no se heredan. Solo se puede hacer referencia a los parámetros de tipo en un tipo por su nombre simple; es decir, no se pueden calificar con el nombre de tipo contenedor. Aunque es un estilo de programación incorrecto, los parámetros de tipo de un tipo anidado pueden ocultar un miembro o parámetro de tipo declarado en el tipo externo:

Class Outer(Of T)
    Class Inner(Of T)
        Public t1 As T    ' Refers to Inner's T
    End Class
End Class

Los tipos y métodos se pueden sobrecargar en función del número de parámetros de tipo (o aridad) que declaran los tipos o métodos. Por ejemplo, las siguientes declaraciones son legales:

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

En el caso de los tipos, las sobrecargas siempre coinciden con el número de argumentos de tipo especificados. Esto resulta útil cuando se usan clases genéricas y no genéricas juntas en el mismo 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

Las reglas para los métodos sobrecargados en los parámetros de tipo se tratan en la sección sobre la resolución de sobrecarga del método.

Dentro de la declaración contenedora, los parámetros de tipo se consideran tipos completos. Dado que se puede crear una instancia de un parámetro de tipo con muchos argumentos de tipo reales diferentes, los parámetros de tipo tienen operaciones y restricciones ligeramente diferentes que otros tipos, como se describe a continuación:

  • Un parámetro de tipo no se puede usar directamente para declarar una clase base o una interfaz.

  • Las reglas para la búsqueda de miembros en parámetros de tipo dependen de las restricciones, si las hay, aplicadas al parámetro de tipo.

  • Las conversiones disponibles para un parámetro de tipo dependen de las restricciones, si las hay, aplicadas a los parámetros de tipo.

  • En ausencia de una Structure restricción, se puede comparar un valor con un tipo representado por un parámetro de tipo mediante NothingIs y IsNot.

  • Un parámetro de tipo solo se puede usar en una New expresión si el parámetro de tipo está restringido por una New restricción o .Structure

  • Un parámetro de tipo no se puede usar en ningún lugar dentro de una excepción de atributo dentro de una GetType expresión.

  • Los parámetros de tipo se pueden usar como argumentos de tipo para otros tipos y parámetros genéricos.

El ejemplo siguiente es un tipo genérico que extiende la Stack(Of ItemType) clase :

Class MyStack(Of ItemType)
    Inherits Stack(Of ItemType)

    Public ReadOnly Property Size() As Integer
        Get
            Return CurrentIndex
        End Get
    End Property
End Class

Cuando una declaración proporciona un argumento de tipo a MyStack, también se aplicará Stack el mismo argumento de tipo.

Como tipo, los parámetros de tipo son puramente una construcción en tiempo de compilación. En tiempo de ejecución, cada parámetro de tipo está enlazado a un tipo en tiempo de ejecución que se especificó proporcionando un argumento de tipo a la declaración genérica. Por lo tanto, el tipo de una variable declarada con un parámetro de tipo será, en tiempo de ejecución, un tipo no genérico o un tipo construido específico. La ejecución en tiempo de ejecución de todas las instrucciones y expresiones que implican parámetros de tipo usa el tipo real que se proporcionó como argumento de tipo para ese parámetro.

Restricciones de tipo

Dado que un argumento de tipo puede ser cualquier tipo en el sistema de tipos, un tipo genérico o un método no puede realizar suposiciones sobre un parámetro de tipo. Por lo tanto, los miembros de un parámetro de tipo se consideran miembros del tipo Object, ya que todos los tipos derivan de Object.

En el caso de una colección como Stack(Of ItemType), este hecho puede no ser una restricción especialmente importante, pero puede haber casos en los que un tipo genérico quiera hacer una suposición sobre los tipos que se proporcionarán como argumentos de tipo. Las restricciones de tipo se pueden colocar en parámetros de tipo que restringen qué tipos se pueden proporcionar como parámetro de tipo y permiten que los tipos o métodos genéricos asuman más sobre los 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

En este ejemplo, restringe DisposableStack(Of ItemType) su parámetro de tipo solo a los tipos que implementan la interfaz System.IDisposable. Como resultado, puede implementar un Dispose método que elimine los objetos que todavía quedan en la cola.

Una restricción de tipo debe ser una de las restricciones Classespeciales , Structureo New, o debe ser un tipo T donde:

  • T es una clase, una interfaz o un parámetro de tipo.

  • T no NotInheritablees .

  • Tno es uno de o un tipo heredado de uno de los siguientes tipos especiales: , , , o System.ValueTypeSystem.Enum. System.MulticastDelegateSystem.DelegateSystem.Array

  • T no Objectes . Dado que todos los tipos derivan de Object, esta restricción no tendría ningún efecto si se permitía.

  • T debe ser al menos tan accesible como el tipo genérico o el método que se declara.

Se pueden especificar varias restricciones de tipo para un único parámetro de tipo mediante la inclusión de las restricciones de tipo en llaves ({}).. Solo una restricción de tipo para un parámetro de tipo determinado puede ser una clase. Es un error combinar una Structure restricción especial con una restricción de clase con nombre o la Class restricción especial.

Class ControlFactory(Of T As {Control, New})
    ...
End Class

Las restricciones de tipo pueden usar los tipos contenedores o cualquiera de los parámetros de tipo de los tipos contenedores. En el ejemplo siguiente, la restricción requiere que el argumento de tipo proporcionado implemente una interfaz genérica con sí misma como argumento de tipo:

Class Sorter(Of V As IComparable(Of V))
    ...
End Class

La restricción Class de tipo especial restringe el argumento de tipo proporcionado a cualquier tipo de referencia.

Nota. Una interfaz puede satisfacer la restricción Class de tipo especial. Y una estructura puede implementar una interfaz. Por lo tanto, la restricción (Of T As U, U As Class) puede satisfacerse con "T" una estructura (que no satisface la Class restricción especial) y "U" una interfaz que implementa (que cumple la Class restricción especial).

La restricción Structure de tipo especial restringe el argumento de tipo proporcionado a cualquier tipo de valor excepto System.Nullable(Of T).

Nota. Las restricciones de estructura no permiten System.Nullable(Of T) para que no sea posible proporcionar System.Nullable(Of T) como argumento de tipo a sí mismo.

La restricción New de tipo especial requiere que el argumento de tipo proporcionado tenga un constructor sin parámetros accesible y no se pueda declarar MustInherit. Por ejemplo:

Class Factory(Of T As New)
    Function CreateInstance() As T
        Return New T()
    End Function
End Class

Una restricción de tipo de clase requiere que el argumento de tipo proporcionado debe ser ese tipo como o heredar de él. Una restricción de tipo de interfaz requiere que el argumento de tipo proporcionado implemente esa interfaz. Una restricción de parámetro de tipo requiere que el argumento de tipo proporcionado se derive o implemente todos los límites especificados para el parámetro de tipo coincidente. Por ejemplo:

Class List(Of T)
    Sub AddRange(Of S As T)(collection As IEnumerable(Of S))
        ...
    End Sub
End Class

En este ejemplo, el parámetro S de tipo de AddRange está restringido al parámetro T de tipo de List. Esto significa que un List(Of Control) elemento restringiría AddRangeel parámetro de tipo a cualquier tipo que sea o herede de Control.

Una restricción Of S As T de parámetro de tipo se resuelve agregando transitivamente todas las restricciones de Ten S, excepto las restricciones especiales (Class, Structure, New). Es un error tener restricciones circulares (por ejemplo, Of S As T, T As S). Es un error tener una restricción de parámetro de tipo que tiene la Structure restricción . Después de agregar restricciones, es posible que se produzcan varias situaciones especiales:

  • Si existen varias restricciones de clase, la clase más derivada se considera la restricción . Si una o varias restricciones de clase no tienen ninguna relación de herencia, la restricción no es satisfactoria y es un error.

  • Si un parámetro de tipo combina una Structure restricción especial con una restricción de clase con nombre o la Class restricción especial, se produce un error. Una restricción de clase puede ser NotInheritable, en cuyo caso no se aceptan tipos derivados de esa restricción y es un error.

El tipo puede ser uno de o un tipo heredado de, los siguientes tipos especiales: System.Array, System.Delegate, System.MulticastDelegate, System.Enumo System.ValueType. En ese caso, solo se acepta el tipo o un tipo heredado de él. Un parámetro de tipo restringido a uno de estos tipos solo puede usar las conversiones permitidas por el DirectCast operador. Por ejemplo:

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

Además, un parámetro de tipo restringido a un tipo de valor debido a una de las relajaciones anteriores no puede llamar a ningún método definido en ese tipo de valor. Por ejemplo:

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

Si la restricción, después de la sustitución, termina como un tipo de matriz, también se permite cualquier tipo de matriz covariante. Por ejemplo:

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

Se considera que un parámetro de tipo con una restricción de clase o interfaz tiene los mismos miembros que esa restricción de clase o interfaz. Si un parámetro de tipo tiene varias restricciones, se considera que el parámetro de tipo tiene la unión de todos los miembros de las restricciones. Si hay miembros con el mismo nombre en más de una restricción, los miembros se ocultan en el orden siguiente: la restricción de clase oculta los miembros en las restricciones de interfaz, que ocultan los miembros en System.ValueType (si Structure se especifica la restricción), que oculta los miembros en Object. Si un miembro con el mismo nombre aparece en más de una restricción de interfaz, el miembro no está disponible (como en la herencia de varias interfaces) y el parámetro type debe convertirse a la interfaz deseada. Por ejemplo:

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

Al proporcionar parámetros de tipo como argumentos de tipo, los parámetros de tipo deben satisfacer las restricciones de los parámetros de tipo coincidentes.

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

Los valores de un parámetro de tipo restringido se pueden usar para tener acceso a los miembros de instancia, incluidos los métodos de instancia, especificados en la restricción.

Interface IPrintable
    Sub Print()
End Interface

Class Printer(Of V As IPrintable)
    Sub PrintOne(v1 As V)
        V1.Print()
    End Sub
End Class

Varianza de parámetro de tipo

Un parámetro de tipo en una interfaz o una declaración de tipo delegado puede especificar opcionalmente un modificador de varianza. Los parámetros de tipo con modificadores de varianza restringen cómo se puede usar el parámetro de tipo en la interfaz o el tipo delegado, pero permiten convertir un tipo de delegado o interfaz genérico a otro tipo genérico con argumentos de tipo compatibles variantes. Por ejemplo:

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

Las interfaces genéricas que tienen parámetros de tipo con modificadores de varianza tienen varias restricciones:

  • No pueden contener una declaración de evento que especifique una lista de parámetros (pero se permite una declaración de evento personalizada o una declaración de evento con un tipo delegado).

  • No pueden contener una clase, estructura o tipo enumerado anidado.

Nota. Estas restricciones se deben al hecho de que los tipos anidados en tipos genéricos copian implícitamente los parámetros genéricos de su elemento primario. En el caso de clases anidadas, estructuras o tipos enumerados, esos tipos de tipos no pueden tener modificadores de varianza en sus parámetros de tipo. En el caso de una declaración de eventos con una lista de parámetros, la clase delegada anidada generada podría tener errores confusos cuando un tipo que parece usarse en una In posición (es decir, un tipo de parámetro) se usa realmente en una Out posición (es decir, el tipo del evento).

Un parámetro de tipo que se declara con el modificador Out es covariante. Informalmente, un parámetro de tipo covariante solo se puede usar en una posición de salida, es decir, un valor que se devuelve desde la interfaz o el tipo delegado, y no se puede usar en una posición de entrada. Se considera que un tipo T es válido de forma covariante si:

  • T es una clase, estructura o tipo enumerado.

  • T es un tipo de interfaz o delegado no genérico.

  • T es un tipo de matriz cuyo tipo de elemento es válido de forma covariante.

  • T es un parámetro de tipo que no se declaró como parámetro Out de tipo.

  • T es un tipo X(Of P1,...,Pn) de delegado o interfaz construido con argumentos de A1,...,An tipo de modo que:

    • Si Pi se declaró como un parámetro de tipo Out, Ai es válido covariantely.

    • Si Pi se declaró como un parámetro de tipo In, Ai es válido contravariantemente.

Lo siguiente debe ser válido de forma covariante en un tipo de interfaz o delegado:

  • Interfaz base de una interfaz.

  • Tipo de valor devuelto de una función o el tipo de delegado.

  • Tipo de una propiedad si hay un Get descriptor de acceso.

  • Tipo de cualquier ByRef parámetro.

Por ejemplo:

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 no es una palabra reservada.

Un parámetro de tipo que se declara con el modificador In es contravariante. Informalmente, un parámetro de tipo contravariante solo se puede usar en una posición de entrada, es decir, un valor que se pasa a la interfaz o al tipo delegado, y no se puede usar en una posición de salida. Se considera que un tipo T es válido contravariantemente si:

  • T es una clase, estructura o tipo enumerado.

  • T es un tipo de interfaz o delegado no genérico.

  • T es un tipo de matriz cuyo tipo de elemento es válido contravariantemente.

  • T es un parámetro de tipo que no se declaró como parámetro in type.

  • T es un tipo X(Of P1,...,Pn) de delegado o interfaz construido con argumentos de A1,...,An tipo de modo que:

    • Si Pi se declaró como un Out parámetro de tipo, Ai es válido contravariantemente.

    • Si Pi se ha declarado como un In parámetro de tipo, Ai es válido covariantely.

Lo siguiente debe ser válido contravariantemente en un tipo de interfaz o delegado:

  • Tipo de un parámetro.

  • Restricción de tipo en un parámetro de tipo de método.

  • Tipo de una propiedad si tiene un Set descriptor de acceso.

  • Tipo de evento.

Por ejemplo:

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

En el caso de que un tipo sea válido de forma contravariante y covariante (por ejemplo, una propiedad con un Get descriptor de acceso y Set o un parámetro), no se puede usar un ByRef parámetro de tipo variant.

La co-y contra-varianza dan lugar a un "problema de ambigüedad de diamantes". Observe el código siguiente:

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()

La clase C se puede convertir en IEnumerable(Of Object) de dos maneras, a través de la conversión de covariante desde IEnumerable(Of String) y hasta la conversión de covariante desde IEnumerable(Of Exception). CLR no especifica a cuál de los dos métodos llamará c.GetEnumerator(). En general, cada vez que se declara una clase para implementar una interfaz covariante con dos argumentos genéricos diferentes que tienen un supertipo común (por ejemplo, en este caso String y Exception tienen el supertipo Objectcomún ), o se declara una clase para implementar una interfaz contravariante con dos argumentos genéricos diferentes que tienen un subtipo común, es probable que surjan ambigüedades. El compilador proporciona una advertencia sobre estas declaraciones.