Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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,
SharedoPrivate).Modificadores a un parámetro (por ejemplo,
ByValoByRef).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,Objectes 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
Bse deriva de un tipoA, es un error para que el tipoAderive directamente o indirectamente del tipoB.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
Sharedes oNotOverridable. - 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 Friendmétodo debe invalidarse mediante unProtectedmétodo si el otro método está en otro ensamblado al que el método de invalidación no tieneFriendacceso. - 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,yOptional, 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
Rcontiene la declaración de introducciónOverridabledeM, se trata de la implementación más derivada deM.De lo contrario, si
Rcontiene una invalidación deM, se trata de la implementación más derivada deM.De lo contrario, la implementación más derivada de
Mes la misma que la de la clase base directa deR.
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
Publicmodificador tienenPublicacceso. No hay restricciones en el uso dePublicentidades.Las entidades declaradas con el
Protectedmodificador tienenProtectedacceso.Protectedel acceso solo se puede especificar en miembros de clases (miembros de tipo normal y clases anidadas) o enOverridablemiembros de módulos y estructuras estándar (que deben heredar, por definición, deSystem.ObjectoSystem.ValueType). UnProtectedmiembro 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.Protectedaccess no es un superconjunto deFriendacceso.Las entidades declaradas con el
Friendmodificador tienenFriendacceso. Solo se puede acceder a una entidad conFriendacceso dentro del programa que contiene la declaración de entidad o los ensamblados a los que se les ha concedidoFriendacceso a través delSystem.Runtime.CompilerServices.InternalsVisibleToAttributeatributo .Las entidades declaradas con los
Protected Friendmodificadores tienen la unión deProtectedyFriendel acceso.Las entidades declaradas con el
Privatemodificador tienenPrivateacceso. Solo se puede acceder a unaPrivateentidad 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
AyA.Xes ilimitado.El dominio de accesibilidad de
A.Y, ,B.XB,B.YB.C, ,B.C.X, yB.C.Yes el programa contenedor.El dominio de accesibilidad de
A.ZesA.El dominio de accesibilidad de
B.Z,B.D,B.D.X, yB.D.YesB, incluidoB.CyB.D.El dominio de accesibilidad de
B.C.ZesB.C.El dominio de accesibilidad de
B.D.ZesB.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:
Resuelva
Ny use las reglas para la resolución de nombres calificada o no calificada.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
Ncompilación.De lo contrario, si
Rcoincide con el nombre de un espacio de nombres en N y no se proporcionó ningún argumento de tipo, oRcoincide con un tipo accesible enNcon 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.De lo contrario, si
Ncontiene uno o varios módulos estándar, yRcoincide 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. SiRcoincide 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.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:
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.
Para cada tipo anidado que contenga la referencia de nombre, empezando por el tipo más interno y pasando al exterior más externo:
- Si
Rcoincide 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. - De lo contrario, si
Rcoincide 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.
- Si
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:
- Si
Rcoincide 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. - De lo contrario, si
Rcoincide 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. - De lo contrario, si el espacio de nombres contiene uno o varios módulos estándar accesibles y
Rcoincide 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. SiRcoincide 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.
- Si
Si el archivo de origen tiene uno o varios alias de importación y
Rcoincide 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.Si el archivo de origen que contiene la referencia de nombre tiene una o varias importaciones:
- Si
Rcoincide 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. SiRcoincide 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. - De lo contrario, si no se proporcionó ninguna lista de argumentos de tipo y
Rcoincide 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 yRcoincide 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. - De lo contrario, si las importaciones contienen uno o varios módulos estándar accesibles, y
Rcoincide 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. SiRcoincide 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.
- Si
Si el entorno de compilación define uno o varios alias de importación y
Rcoincide 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.Si el entorno de compilación define una o varias importaciones:
- Si
Rcoincide 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. SiRcoincide 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. - De lo contrario, si no se proporcionó ninguna lista de argumentos de tipo y
Rcoincide 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 yRcoincide 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. - De lo contrario, si las importaciones contienen uno o varios módulos estándar accesibles, y
Rcoincide 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. SiRcoincide 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.
- Si
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
Structurerestricción, se puede comparar un valor con un tipo representado por un parámetro de tipo medianteNothingIsyIsNot.Un parámetro de tipo solo se puede usar en una
Newexpresión si el parámetro de tipo está restringido por unaNewrestricción o .StructureUn parámetro de tipo no se puede usar en ningún lugar dentro de una excepción de atributo dentro de una
GetTypeexpresió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:
Tes una clase, una interfaz o un parámetro de tipo.TnoNotInheritablees .Tno es uno de o un tipo heredado de uno de los siguientes tipos especiales: , , , oSystem.ValueTypeSystem.Enum.System.MulticastDelegateSystem.DelegateSystem.ArrayTnoObjectes . Dado que todos los tipos derivan deObject, esta restricción no tendría ningún efecto si se permitía.Tdebe 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
Structurerestricción especial con una restricción de clase con nombre o laClassrestricción especial, se produce un error. Una restricción de clase puede serNotInheritable, 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:
Tes una clase, estructura o tipo enumerado.Tes un tipo de interfaz o delegado no genérico.Tes un tipo de matriz cuyo tipo de elemento es válido de forma covariante.Tes un parámetro de tipo que no se declaró como parámetroOutde tipo.Tes un tipoX(Of P1,...,Pn)de delegado o interfaz construido con argumentos deA1,...,Antipo de modo que:Si
Pise declaró como un parámetro de tipo Out,Aies válido covariantely.Si
Pise declaró como un parámetro de tipo In,Aies 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
Getdescriptor de acceso.Tipo de cualquier
ByRefpará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:
Tes una clase, estructura o tipo enumerado.Tes un tipo de interfaz o delegado no genérico.Tes un tipo de matriz cuyo tipo de elemento es válido contravariantemente.Tes un parámetro de tipo que no se declaró como parámetro in type.Tes un tipoX(Of P1,...,Pn)de delegado o interfaz construido con argumentos deA1,...,Antipo de modo que:Si
Pise declaró como unOutparámetro de tipo,Aies válido contravariantemente.Si
Pise ha declarado como unInparámetro de tipo,Aies 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
Setdescriptor 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.
Visual Basic language spec