Compartir a través de


Tipos en Visual Basic

Las dos categorías fundamentales de tipos de Visual Basic son tipos de valor y tipos de referencia. Los tipos primitivos (excepto cadenas), las enumeraciones y las estructuras son tipos de valor. Las clases, cadenas, módulos estándar, interfaces, matrices y delegados son tipos de referencia.

Cada tipo tiene un valor predeterminado, que es el valor que se asigna a las variables de ese tipo tras la inicialización.

TypeName
    : ArrayTypeName
    | NonArrayTypeName
    ;

NonArrayTypeName
    : SimpleTypeName
    | NullableTypeName
    ;

SimpleTypeName
    : QualifiedTypeName
    | BuiltInTypeName
    ;

QualifiedTypeName
    : Identifier TypeArguments? (Period IdentifierOrKeyword TypeArguments?)*
    | 'Global' Period IdentifierOrKeyword TypeArguments?
      (Period IdentifierOrKeyword TypeArguments?)*
    ;

TypeArguments
    : OpenParenthesis 'Of' TypeArgumentList CloseParenthesis
    ;

TypeArgumentList
    : TypeName ( Comma TypeName )*
    ;

BuiltInTypeName
    : 'Object'
    | PrimitiveTypeName
    ;

TypeModifier
    : AccessModifier
    | 'Shadows'
    ;

IdentifierModifiers
    : NullableNameModifier? ArrayNameModifier?
    ;

Tipos de valor y tipos de referencia

Aunque los tipos de valor y los tipos de referencia pueden ser similares en términos de sintaxis de declaración y uso, su semántica es distinta.

Los tipos de referencia se almacenan en el montón en tiempo de ejecución; Solo se puede acceder a ellos a través de una referencia a ese almacenamiento. Dado que siempre se accede a los tipos de referencia a través de referencias, la duración se administra mediante .NET Framework. Se realiza un seguimiento de las referencias pendientes a una instancia determinada y la instancia solo se destruye cuando no quedan más referencias. Una variable de tipo de referencia contiene una referencia a un valor de ese tipo, un valor de un tipo más derivado o un valor NULL. Un valor NULL hace referencia a nada; no es posible hacer nada con un valor NULL, excepto asignarlo. La asignación a una variable de un tipo de referencia crea una copia de la referencia en lugar de una copia del valor al que se hace referencia. Para una variable de un tipo de referencia, el valor predeterminado es un valor NULL.

Los tipos de valor se almacenan directamente en la pila, ya sea dentro de una matriz o dentro de otro tipo; solo se puede acceder directamente al almacenamiento. Dado que los tipos de valor se almacenan directamente dentro de las variables, su duración viene determinada por la duración de la variable que las contiene. Cuando se destruye la ubicación que contiene una instancia de tipo de valor, también se destruye la instancia de tipo de valor. Los tipos de valor siempre se acceden directamente; no es posible crear una referencia a un tipo de valor. Prohibir dicha referencia hace imposible hacer referencia a una instancia de clase de valor que se ha destruido. Dado que los tipos de valor siempre NotInheritableson , una variable de un tipo de valor siempre contiene un valor de ese tipo. Por este motivo, el valor de un tipo de valor no puede ser un valor NULL ni puede hacer referencia a un objeto de un tipo más derivado. La asignación a una variable de un tipo de valor crea una copia del valor que se asigna. Para una variable de un tipo de valor, el valor predeterminado es el resultado de inicializar cada miembro de variable del tipo en su valor predeterminado.

En el ejemplo siguiente se muestra la diferencia entre los tipos de referencia y los tipos de valor:

Class Class1
    Public Value As Integer = 0
End Class

Module Test
    Sub Main()
        Dim val1 As Integer = 0
        Dim val2 As Integer = val1
        val2 = 123
        Dim ref1 As Class1 = New Class1()
        Dim ref2 As Class1 = ref1
        ref2.Value = 123
        Console.WriteLine("Values: " & val1 & ", " & val2)
        Console.WriteLine("Refs: " & ref1.Value & ", " & ref2.Value)
    End Sub
End Module

La salida del programa es:

Values: 0, 123
Refs: 123, 123

La asignación a la variable val2 local no afecta a la variable val1 local porque ambas variables locales son de un tipo de valor (el tipo Integer) y cada variable local de un tipo de valor tiene su propio almacenamiento. En cambio, la asignación ref2.Value = 123; afecta al objeto que tanto como ref1ref2 hace referencia.

Una cosa que debe tener en cuenta sobre el sistema de tipos de .NET Framework es que aunque las estructuras, las enumeraciones y los tipos primitivos (excepto para String) son tipos de valor, todos heredan de los tipos de referencia. Las estructuras y los tipos primitivos heredan del tipo System.ValueTypede referencia , que hereda de Object. Los tipos enumerados heredan del tipo System.Enumde referencia , que hereda de System.ValueType.

Tipos de valor nulables

Para los tipos de valor, se puede agregar un ? modificador a un nombre de tipo para representar la versión que acepta valores NULL de ese tipo.

NullableTypeName
    : NonArrayTypeName '?'
    ;

NullableNameModifier
    : '?'
    ;

Un tipo de valor que acepta valores NULL puede contener los mismos valores que la versión que no acepta valores NULL del tipo, así como el valor NULL. Por lo tanto, para un tipo de valor que acepta valores NULL, la asignación Nothing a una variable del tipo establece el valor de la variable en el valor NULL, no el valor cero del tipo de valor. Por ejemplo:

Dim x As Integer = Nothing
Dim y As Integer? = Nothing

' Prints zero
Console.WriteLine(x)
' Prints nothing (because the value of y is the null value)
Console.WriteLine(y)

Una variable también se puede declarar como de un tipo de valor que acepta valores NULL colocando un modificador de tipo que acepta valores NULL en el nombre de la variable. Para mayor claridad, no es válido tener un modificador de tipo que acepta valores NULL en un nombre de variable y un nombre de tipo en la misma declaración. Dado que los tipos que aceptan valores NULL se implementan mediante el tipo System.Nullable(Of T), el tipo T? es sinónimo del tipo System.Nullable(Of T)y los dos nombres se pueden usar indistintamente. El ? modificador no se puede colocar en un tipo que ya acepta valores NULL; por lo tanto, no es posible declarar el tipo Integer?? o System.Nullable(Of Integer)?.

Un tipo T? de valor que acepta valores NULL tiene los miembros de System.Nullable(Of T) , así como los operadores o conversiones que se levantan del tipo T subyacente en el tipo T?. Levantar operadores y conversiones del tipo subyacente, en la mayoría de los casos sustituyendo los tipos de valor que aceptan valores NULL para los tipos de valor que no aceptan valores NULL. Esto permite también muchas de las mismas conversiones y operaciones que se aplican a T para aplicarlas T? .

Implementación de interfaz

Las declaraciones de estructura y clase pueden declarar que implementan un conjunto de tipos de interfaz a través de una o varias Implements cláusulas.

TypeImplementsClause
    : 'Implements' TypeImplements StatementTerminator
    ;

TypeImplements
    : NonArrayTypeName ( Comma NonArrayTypeName )*
    ;

Todos los tipos especificados en la Implements cláusula deben ser interfaces y el tipo debe implementar todos los miembros de las interfaces. Por ejemplo:

Interface ICloneable
    Function Clone() As Object
End Interface 

Interface IComparable
    Function CompareTo(other As Object) As Integer
End Interface 

Structure ListEntry
    Implements ICloneable, IComparable

    ...

    Public Function Clone() As Object Implements ICloneable.Clone
        ...
    End Function 

    Public Function CompareTo(other As Object) As Integer _
        Implements IComparable.CompareTo
        ...
    End Function 
End Structure

Un tipo que implementa una interfaz también implementa implícitamente todas las interfaces base de la interfaz. Esto es true incluso si el tipo no enumera explícitamente todas las interfaces base de la Implements cláusula . En este ejemplo, la TextBox estructura implementa y IControlITextBox.

Interface IControl
    Sub Paint()
End Interface 

Interface ITextBox
    Inherits IControl

    Sub SetText(text As String)
End Interface 

Structure TextBox
    Implements ITextBox

    ...

    Public Sub Paint() Implements ITextBox.Paint
        ...
    End Sub

    Public Sub SetText(text As String) Implements ITextBox.SetText
        ...
    End Sub
End Structure

Declarar que un tipo implementa una interfaz en y de sí misma no declara nada en el espacio de declaración del tipo. Por lo tanto, es válido implementar dos interfaces con un método con el mismo nombre.

Los tipos no pueden implementar un parámetro de tipo por sí mismo, aunque puede implicar los parámetros de tipo que están en el ámbito.

Class C1(Of V)
    Implements V  ' Error, can't implement type parameter directly
    Implements IEnumerable(Of V)  ' OK, not directly implementing

    ...
End Class

Las interfaces genéricas se pueden implementar varias veces mediante distintos argumentos de tipo. Sin embargo, un tipo genérico no puede implementar una interfaz genérica mediante un parámetro de tipo si el parámetro de tipo proporcionado (independientemente de las restricciones de tipo) podría superponerse con otra implementación de esa interfaz. Por ejemplo:

Interface I1(Of T)
End Interface

Class C1
    Implements I1(Of Integer)
    Implements I1(Of Double)    ' OK, no overlap
End Class

Class C2(Of T)
    Implements I1(Of Integer)
    Implements I1(Of T)         ' Error, T could be Integer
End Class

Tipos primitivos

Los tipos primitivos se identifican a través de palabras clave, que son alias para tipos predefinidos en el System espacio de nombres. Un tipo primitivo es completamente indistinguible del tipo que alias: escribir la palabra Byte reservada es exactamente igual que escribir System.Byte. Los tipos primitivos también se conocen como tipos intrínsecos.

PrimitiveTypeName
    : NumericTypeName
    | 'Boolean'
    | 'Date'
    | 'Char'
    | 'String'
    ;

NumericTypeName
    : IntegralTypeName
    | FloatingPointTypeName
    | 'Decimal'
    ;

IntegralTypeName
    : 'Byte' | 'SByte' | 'UShort' | 'Short' | 'UInteger'
    | 'Integer' | 'ULong' | 'Long'
    ;

FloatingPointTypeName
    : 'Single' | 'Double'
    ;

Dado que un tipo primitivo aliasa un tipo normal, cada tipo primitivo tiene miembros. Por ejemplo, Integer tiene los miembros declarados en System.Int32. Los literales se pueden tratar como instancias de sus tipos correspondientes.

  • Los tipos primitivos difieren de otros tipos de estructura en que permiten ciertas operaciones adicionales:

  • Los tipos primitivos permiten crear valores mediante la escritura de literales. Por ejemplo, 123I es un literal de tipo Integer.

  • Es posible declarar constantes de los tipos primitivos.

  • Cuando los operandos de una expresión son constantes de tipo primitivo, es posible que el compilador evalúe la expresión en tiempo de compilación. Esta expresión se conoce como expresión constante.

Visual Basic define los siguientes tipos primitivos:

  • Los tipos Byte de valor entero (entero sin signo de 1 byte), SByte (entero con signo de 1 byte), UShort (entero sin signo de 2 bytes), (entero con signo de 2 bytes), Short (entero con signo de 4 bytes), UInteger (entero con signo de 4 bytes), Integer (entero con signo de 4 bytes), (entero con signo de 8 bytes) ULong y Long (entero con signo de 8 bytes). Estos tipos se asignan a System.Byte, System.SByte, System.UInt16, System.Int16System.UInt32, System.Int32, y System.UInt64System.Int64, respectivamente. El valor predeterminado de un tipo entero es equivalente al literal 0.

  • Los tipos Single de valor de punto flotante (punto flotante de 4 bytes) y Double (punto flotante de 8 bytes). Estos tipos se asignan a System.Single y System.Double, respectivamente. El valor predeterminado de un tipo de punto flotante es equivalente al literal 0.

  • Tipo Decimal (valor decimal de 16 bytes), que se asigna a System.Decimal. El valor predeterminado de decimal es equivalente al literal 0D.

  • Tipo Boolean de valor, que representa un valor verdadero, normalmente el resultado de una operación relacional o lógica. El literal es de tipo System.Boolean. El valor predeterminado del Boolean tipo es equivalente al literal False.

  • El Date tipo de valor, que representa una fecha o una hora y se asigna a System.DateTime. El valor predeterminado del Date tipo es equivalente al literal # 01/01/0001 12:00:00AM #.

  • El Char tipo de valor, que representa un único carácter Unicode y se asigna a System.Char. El valor predeterminado del Char tipo es equivalente a la expresión ChrW(0)constante .

  • El String tipo de referencia, que representa una secuencia de caracteres Unicode y se asigna a System.String. El valor predeterminado del String tipo es un valor NULL.

Enumeraciones

Las enumeraciones son tipos de valor que heredan de System.Enum y representan simbólicamente un conjunto de valores de uno de los tipos enteros primitivos.

EnumDeclaration
    : Attributes? TypeModifier* 'Enum' Identifier
      ( 'As' NonArrayTypeName )? StatementTerminator
      EnumMemberDeclaration+
      'End' 'Enum' StatementTerminator
    ;

Para un tipo Ede enumeración , el valor predeterminado es el valor generado por la expresión CType(0, E).

El tipo subyacente de una enumeración debe ser un tipo entero que pueda representar todos los valores del enumerador definidos en la enumeración. Si se especifica un tipo subyacente, debe ser Byte, SByte, UIntegerIntegerShortUShort, , ULong, Longo uno de sus tipos correspondientes en el System espacio de nombres. Si no se especifica explícitamente ningún tipo subyacente, el valor predeterminado es Integer.

En el ejemplo siguiente se declara una enumeración con un tipo subyacente de Long:

Enum Color As Long
    Red
    Green
    Blue
End Enum

Un desarrollador puede optar por usar un tipo subyacente de Long, como en el ejemplo, para habilitar el uso de valores que están en el intervalo de Long, pero no en el intervalo de Integer, o para conservar esta opción para el futuro.

Miembros de enumeración

Los miembros de una enumeración son los valores enumerados declarados en la enumeración y los miembros heredados de la clase System.Enum.

El ámbito de un miembro de enumeración es el cuerpo de la declaración de enumeración. Esto significa que fuera de una declaración de enumeración, un miembro de enumeración siempre debe estar calificado (a menos que el tipo se importe específicamente en un espacio de nombres a través de una importación de espacio de nombres).

El orden de declaración de las declaraciones de miembros de enumeración es significativo cuando se omiten los valores de expresión constantes. Los miembros de enumeración tienen Public acceso implícitamente; no se permiten modificadores de acceso en declaraciones de miembro de enumeración.

EnumMemberDeclaration
    : Attributes? Identifier ( Equals ConstantExpression )? StatementTerminator
    ;

Valores de enumeración

Los valores enumerados de una lista de miembros de enumeración se declaran como constantes tipadas como el tipo de enumeración subyacente y pueden aparecer siempre que se requieran constantes. Una definición de miembro de enumeración con = proporciona al miembro asociado el valor indicado por la expresión constante. La expresión constante debe evaluarse como un tipo entero que se puede convertir implícitamente en el tipo subyacente y debe estar dentro del intervalo de valores que el tipo subyacente puede representar. En el ejemplo siguiente se produce un error porque los valores 1.5constantes , 2.3y 3.3 no se convierten implícitamente en el tipo Long entero subyacente con semántica estricta.

Option Strict On

Enum Color As Long
    Red = 1.5
    Green = 2.3
    Blue = 3.3
End Enum

Varios miembros de enumeración pueden compartir el mismo valor asociado, como se muestra a continuación:

Enum Color
    Red
    Green
    Blue
    Max = Blue
End Enum

En el ejemplo se muestra una enumeración que tiene dos miembros de enumeración -- Blue y Max -- que tienen el mismo valor asociado.

Si la primera definición de valor del enumerador de la enumeración no tiene inicializador, el valor de la constante correspondiente es 0. Una definición de valor de enumeración sin un inicializador proporciona al enumerador el valor obtenido aumentando el valor del valor de enumeración anterior por 1. Este valor aumentado debe estar dentro del intervalo de valores que el tipo subyacente puede representar.

Enum Color
    Red
    Green = 10
    Blue
End Enum 

Module Test
    Sub Main()
        Console.WriteLine(StringFromColor(Color.Red))
        Console.WriteLine(StringFromColor(Color.Green))
        Console.WriteLine(StringFromColor(Color.Blue))
    End Sub

    Function StringFromColor(c As Color) As String
        Select Case c
            Case Color.Red
                Return String.Format("Red = " & CInt(c))

            Case Color.Green
                Return String.Format("Green = " & CInt(c))

            Case Color.Blue
                Return String.Format("Blue = " & CInt(c))

            Case Else
                Return "Invalid color"
        End Select
    End Function
End Module

En el ejemplo anterior se imprimen los valores de enumeración y sus valores asociados. La salida es la siguiente:

Red = 0
Green = 10
Blue = 11

Los motivos de los valores son los siguientes:

  • El valor Red de enumeración se asigna automáticamente al valor 0 (ya que no tiene inicializador y es el primer miembro de valor de enumeración).

  • El valor Green de enumeración se asigna explícitamente al valor 10.

  • El valor Blue de enumeración se asigna automáticamente al valor uno mayor que el valor de enumeración que lo precede textualmente.

La expresión constante no puede usar directa o indirectamente el valor de su propio valor de enumeración asociado (es decir, no se permite la circularidad en la expresión constante). El ejemplo siguiente no es válido porque las declaraciones de A y B son circulares.

Enum Circular
    A = B
    B
End Enum

A depende explícitamente B de y B depende implícitamente A .

Las clases

Una clase es una estructura de datos que puede contener miembros de datos (constantes, variables y eventos), miembros de función (métodos, propiedades, indizadores, operadores y constructores) y tipos anidados. Las clases son tipos de referencia.

ClassDeclaration
    : Attributes? ClassModifier* 'Class' Identifier TypeParameterList? StatementTerminator
      ClassBase?
      TypeImplementsClause*
      ClassMemberDeclaration*
      'End' 'Class' StatementTerminator
    ;

ClassModifier
    : TypeModifier
    | 'MustInherit'
    | 'NotInheritable'
    | 'Partial'
    ;

En el ejemplo siguiente se muestra una clase que contiene cada tipo de miembro:

Class AClass
    Public Sub New()
        Console.WriteLine("Constructor")
    End Sub

    Public Sub New(value As Integer)
        MyVariable = value
        Console.WriteLine("Constructor")
    End Sub

    Public Const MyConst As Integer = 12
    Public MyVariable As Integer = 34

    Public Sub MyMethod()
        Console.WriteLine("MyClass.MyMethod")
    End Sub

    Public Property MyProperty() As Integer
        Get
            Return MyVariable
        End Get

        Set (value As Integer)
            MyVariable = value
        End Set
    End Property

    Default Public Property Item(index As Integer) As Integer
        Get
            Return 0
        End Get

        Set (value As Integer)
            Console.WriteLine("Item(" & index & ") = " & value)
        End Set
    End Property

    Public Event MyEvent()

    Friend Class MyNestedClass
    End Class 
End Class

En el ejemplo siguiente se muestran los usos de estos miembros:

Module Test

    ' Event usage.
    Dim WithEvents aInstance As AClass

    Sub Main()
        ' Constructor usage.
        Dim a As AClass = New AClass()
        Dim b As AClass = New AClass(123)

        ' Constant usage.
        Console.WriteLine("MyConst = " & AClass.MyConst)

        ' Variable usage.
        a.MyVariable += 1
        Console.WriteLine("a.MyVariable = " & a.MyVariable)

        ' Method usage.
        a.MyMethod()

        ' Property usage.
        a.MyProperty += 1
        Console.WriteLine("a.MyProperty = " & a.MyProperty)
        a(1) = 1

        ' Event usage.
        aInstance = a
    End Sub 

    Sub MyHandler() Handles aInstance.MyEvent
        Console.WriteLine("Test.MyHandler")
    End Sub 
End Module

Hay dos modificadores específicos de clase y MustInheritNotInheritable. No es válido especificar ambos.

Especificación base de clase

Una declaración de clase puede incluir una especificación de tipo base que define el tipo base directo de la clase.

ClassBase
    : 'Inherits' NonArrayTypeName StatementTerminator
    ;

Si una declaración de clase no tiene ningún tipo base explícito, el tipo base directo es implícitamente Object. Por ejemplo:

Class Base
End Class

Class Derived
    Inherits Base
End Class

Los tipos base no pueden ser un parámetro de tipo propio, aunque puede implicar los parámetros de tipo que están en el ámbito.

Class C1(Of V) 
End Class

Class C2(Of V)
    Inherits V    ' Error, type parameter used as base class 
End Class

Class C3(Of V)
    Inherits C1(Of V)    ' OK: not directly inheriting from V.
End Class

Las clases solo pueden derivar de Object clases y . No es válido para que una clase derive de System.ValueType, System.Enum, System.ArraySystem.MulticastDelegate o System.Delegate. Una clase genérica no puede derivar de System.Attribute o de una clase que derive de ella.

Cada clase tiene exactamente una clase base directa y se prohíbe la circularidad en la derivación. No es posible derivar de una NotInheritable clase y el dominio de accesibilidad de la clase base debe ser el mismo que o un superconjunto del dominio de accesibilidad de la propia clase.

Miembros de clase

Los miembros de una clase constan de los miembros introducidos por sus declaraciones de miembro de clase y los miembros heredados de su clase base directa.

ClassMemberDeclaration
    : NonModuleDeclaration
    | EventMemberDeclaration
    | VariableMemberDeclaration
    | ConstantMemberDeclaration
    | MethodMemberDeclaration
    | PropertyMemberDeclaration
    | ConstructorMemberDeclaration
    | OperatorDeclaration
    ;

Una declaración de miembro de clase puede tener Publicacceso a , Protected, Friend, , Protected Friendo Private . Cuando una declaración de miembro de clase no incluye un modificador de acceso, la declaración tiene como valor predeterminado Public tener acceso, a menos que sea una declaración variable; en ese caso, el valor predeterminado es Private tener acceso.

El ámbito de un miembro de clase es el cuerpo de clase en el que se produce la declaración de miembro, además de la lista de restricciones de esa clase (si es genérica y tiene restricciones). Si el miembro tiene Friend acceso, su ámbito se extiende al cuerpo de clase de cualquier clase derivada en el mismo programa o en cualquier ensamblado al que se le haya concedido Friend acceso y, si el miembro tiene Public, Protectedo Protected Friend acceso, su ámbito se extiende al cuerpo de clase de cualquier clase derivada en cualquier programa.

Estructuras

Las estructuras son tipos de valor que heredan de System.ValueType. Las estructuras son similares a las clases de que representan estructuras de datos que pueden contener miembros de datos y miembros de función. Sin embargo, a diferencia de las clases, las estructuras no requieren asignación de montón.

StructureDeclaration
    : Attributes? StructureModifier* 'Structure' Identifier
      TypeParameterList? StatementTerminator
      TypeImplementsClause*
      StructMemberDeclaration*
      'End' 'Structure' StatementTerminator
    ;

StructureModifier
    : TypeModifier
    | 'Partial'
    ;

En el caso de las clases, es posible que dos variables hagan referencia al mismo objeto y, por tanto, es posible que las operaciones de una variable afecten al objeto al que hace referencia la otra variable. Con las estructuras, las variables tienen su propia copia de los datos que noShared son, por lo que no es posible que las operaciones de una afecten a la otra, como se muestra en el ejemplo siguiente:

Structure Point
    Public x, y As Integer

    Public Sub New(x As Integer, y As Integer)
        Me.x = x
        Me.y = y
    End Sub
End Structure

Dada la declaración anterior, el código siguiente genera el valor 10:

Module Test
    Sub Main()
        Dim a As New Point(10, 10)
        Dim b As Point = a

        a.x = 100
        Console.WriteLine(b.x)
    End Sub
End Module

La asignación de a para b crea una copia del valor y b , por tanto, no se ve afectada por la asignación a a.x. En su lugar, se había Point declarado como una clase, la salida sería 100 porque a y b haría referencia al mismo objeto.

Miembros de la estructura

Los miembros de una estructura son los miembros introducidos por sus declaraciones de miembro de estructura y los miembros heredados de System.ValueType.

StructMemberDeclaration
    : NonModuleDeclaration
    | VariableMemberDeclaration
    | ConstantMemberDeclaration
    | EventMemberDeclaration
    | MethodMemberDeclaration
    | PropertyMemberDeclaration
    | ConstructorMemberDeclaration
    | OperatorDeclaration
    ;

Cada estructura tiene implícitamente un Public constructor de instancia sin parámetros que genera el valor predeterminado de la estructura. Como resultado, no es posible que una declaración de tipo de estructura declare un constructor de instancia sin parámetros. Sin embargo, un tipo de estructura tiene permiso para declarar constructores de instancia con parámetros , como en el ejemplo siguiente:

Structure Point
    Private x, y As Integer

    Public Sub New(x As Integer, y As Integer)
        Me.x = x
        Me.y = y
    End Sub
End Structure

Dada la declaración anterior, las siguientes instrucciones crean un Point con x e y inicializado en cero.

Dim p1 As Point = New Point()
Dim p2 As Point = New Point(0, 0)

Dado que las estructuras contienen directamente sus valores de campo (en lugar de referencias a esos valores), las estructuras no pueden contener campos que se hagan referencia directa o indirectamente a sí mismos. Por ejemplo, el código siguiente no es válido:

Structure S1
    Dim f1 As S2
End Structure

Structure S2
    ' This would require S1 to contain itself.
    Dim f1 As S1
End Structure

Normalmente, una declaración de miembro de estructura solo puede tener Publicacceso a , Friendo Private , pero al invalidar miembros heredados de Objecty ProtectedProtected Friend también se puede usar el acceso. Cuando una declaración de miembro de estructura no incluye un modificador de acceso, la declaración tiene Public como valor predeterminado el acceso. El ámbito de un miembro declarado por una estructura es el cuerpo de la estructura en el que se produce la declaración, además de las restricciones de esa estructura (si era genérica y tenía restricciones).

Módulos estándar

Un módulo estándar es un tipo cuyos miembros se limitan implícitamente Shared y se limitan al espacio de declaración del espacio de nombres que contiene el módulo estándar, en lugar de simplemente a la propia declaración del módulo estándar. Es posible que nunca se cree una instancia de los módulos estándar. Es un error declarar una variable de un tipo de módulo estándar.

ModuleDeclaration
    : Attributes? TypeModifier* 'Module' Identifier StatementTerminator
      ModuleMemberDeclaration*
      'End' 'Module' StatementTerminator
    ;

Un miembro de un módulo estándar tiene dos nombres completos, uno sin el nombre del módulo estándar y otro con el nombre del módulo estándar. Más de un módulo estándar de un espacio de nombres puede definir un miembro con un nombre determinado; las referencias no calificadas al nombre fuera de cualquiera de los módulos son ambiguas. Por ejemplo:

Namespace N1
    Module M1
        Sub S1()
        End Sub

        Sub S2()
        End Sub
    End Module

    Module M2
        Sub S2()
        End Sub
    End Module

    Module M3
        Sub Main()
            S1()       ' Valid: Calls N1.M1.S1.
            N1.S1()    ' Valid: Calls N1.M1.S1.
            S2()       ' Not valid: ambiguous.
            N1.S2()    ' Not valid: ambiguous.
            N1.M2.S2() ' Valid: Calls N1.M2.S2.
        End Sub
    End Module
End Namespace

Un módulo solo se puede declarar en un espacio de nombres y no se puede anidar en otro tipo. Es posible que los módulos estándar no implementen interfaces, derivan implícitamente de Objecty solo Shared tienen constructores.

Miembros del módulo estándar

Los miembros de un módulo estándar son los miembros introducidos por sus declaraciones de miembro y los miembros heredados de Object. Los módulos estándar pueden tener cualquier tipo de miembro, excepto los constructores de instancia. Todos los miembros de tipo de módulo estándar son implícitamente Shared.

ModuleMemberDeclaration
    : NonModuleDeclaration
    | VariableMemberDeclaration
    | ConstantMemberDeclaration
    | EventMemberDeclaration
    | MethodMemberDeclaration
    | PropertyMemberDeclaration
    | ConstructorMemberDeclaration
    ;

Normalmente, una declaración de miembro del módulo estándar solo puede tener Publicacceso a , Friendo Private , pero al invalidar miembros heredados de Object, se pueden especificar los Protected modificadores de acceso y Protected Friend . Cuando una declaración de miembro del módulo estándar no incluye un modificador de acceso, la declaración tiene como valor predeterminado Public tener acceso, a menos que sea una variable, que tiene Private como valor predeterminado acceder.

Como se indicó anteriormente, el ámbito de un miembro de módulo estándar es la declaración que contiene la declaración del módulo estándar. Los miembros heredados de Object no se incluyen en este ámbito especial; esos miembros no tienen ningún ámbito y siempre deben calificarse con el nombre del módulo. Si el miembro tiene Friend acceso, su ámbito solo se extiende a los miembros del espacio de nombres declarados en el mismo programa o ensamblados a los que se les ha concedido Friend acceso.

Interfaces

Las interfaces son tipos de referencia que otros tipos implementan para garantizar que admiten determinados métodos. Una interfaz nunca se crea directamente y no tiene ninguna representación real: otros tipos se deben convertir en un tipo de interfaz. Una interfaz define un contrato. Una clase o estructura que implementa una interfaz debe cumplir su contrato.

InterfaceDeclaration
    : Attributes? TypeModifier* 'Interface' Identifier
      TypeParameterList? StatementTerminator
      InterfaceBase*
      InterfaceMemberDeclaration*
      'End' 'Interface' StatementTerminator
    ;

En el ejemplo siguiente se muestra una interfaz que contiene una propiedad Itempredeterminada , un evento E, un método Fy una propiedad P:

Interface IExample
    Default Property Item(index As Integer) As String

    Event E()

    Sub F(value As Integer)

    Property P() As String
End Interface

Las interfaces pueden emplear varias herencias. En el ejemplo siguiente, la interfaz IComboBox hereda de y ITextBoxIListBox:

Interface IControl
    Sub Paint()
End Interface 

Interface ITextBox
    Inherits IControl

    Sub SetText(text As String)
End Interface 

Interface IListBox
    Inherits IControl

    Sub SetItems(items() As String)
End Interface 

Interface IComboBox
    Inherits ITextBox, IListBox 
End Interface

Las clases y estructuras pueden implementar varias interfaces. En el ejemplo siguiente, la clase EditBox deriva de la clase Control e implementa y IControlIDataBound:

Interface IDataBound
    Sub Bind(b As Binder)
End Interface 

Public Class EditBox
    Inherits Control
    Implements IControl, IDataBound

    Public Sub Paint() Implements IControl.Paint
        ...
    End Sub

    Public Sub Bind(b As Binder) Implements IDataBound.Bind
        ...
    End Sub
End Class

Herencia de interfaz

Las interfaces base de una interfaz son las interfaces base explícitas y sus interfaces base. En otras palabras, el conjunto de interfaces base es el cierre transitivo completo de las interfaces base explícitas, de sus propias interfaces base explícitas, y así sucesivamente. Si una declaración de interfaz no tiene ninguna base de interfaz explícita, no hay ninguna interfaz base para el tipo : las interfaces no heredan de Object (aunque tienen una conversión de ampliación a Object).

InterfaceBase
    : 'Inherits' InterfaceBases StatementTerminator
    ;

InterfaceBases
    : NonArrayTypeName ( Comma NonArrayTypeName )*
    ;

En el ejemplo siguiente, las interfaces base de IComboBox son IControl, ITextBoxy IListBox.

Interface IControl
    Sub Paint()
End Interface 

Interface ITextBox
    Inherits IControl

    Sub SetText(text As String)
End Interface 

Interface IListBox
    Inherits IControl

    Sub SetItems(items() As String)
End Interface 

Interface IComboBox
    Inherits ITextBox, IListBox 
End Interface

Una interfaz hereda todos los miembros de sus interfaces base. En otras palabras, la interfaz IComboBox anterior hereda los miembros SetText y SetItems, así como Paint.

Una clase o estructura que implementa una interfaz también implementa implícitamente todas las interfaces base de la interfaz.

Si una interfaz aparece más de una vez en el cierre transitivo de las interfaces base, solo contribuye sus miembros a la interfaz derivada una vez. Un tipo que implementa la interfaz derivada solo tiene que implementar los métodos de la interfaz base definida de multiplicación una vez. En el ejemplo siguiente, Paint solo debe implementarse una vez, aunque la clase implemente IComboBox y IControl.

Class ComboBox
    Implements IControl, IComboBox

    Sub SetText(text As String) Implements IComboBox.SetText
    End Sub

    Sub SetItems(items() As String) Implements IComboBox.SetItems
    End Sub

    Sub Print() Implements IComboBox.Paint
    End Sub
End Class

Una Inherits cláusula no tiene ningún efecto en otras Inherits cláusulas. En el ejemplo siguiente, IDerived debe calificar el nombre de INested con IBase.

Interface IBase
    Interface INested
        Sub Nested()
    End Interface

    Sub Base()
End Interface

Interface IDerived
    Inherits IBase, INested   ' Error: Must specify IBase.INested.
End Interface

El dominio de accesibilidad de una interfaz base debe ser el mismo que o un superconjunto del dominio de accesibilidad de la propia interfaz.

Miembros de interfaz

Los miembros de una interfaz constan de los miembros introducidos por sus declaraciones de miembro y los miembros heredados de sus interfaces base.

InterfaceMemberDeclaration
    : NonModuleDeclaration
    | InterfaceEventMemberDeclaration
    | InterfaceMethodMemberDeclaration
    | InterfacePropertyMemberDeclaration
    ;

Aunque las interfaces no heredan miembros de Object, porque todas las clases o estructuras que implementan una interfaz heredan de Object, los miembros de , incluidos los métodos de extensión, se consideran miembros de una interfaz y se pueden llamar directamente en una interfaz sin necesidad de Objectconvertir a Object. Por ejemplo:

Interface I1
End Interface

Class C1
    Implements I1
End Class

Module Test
    Sub Main()
        Dim i As I1 = New C1()
        Dim h As Integer = i.GetHashCode()
    End Sub
End Module

Miembros de una interfaz con el mismo nombre que los miembros de miembros de Object instantáneas Object implícitamente. Solo los tipos, métodos, propiedades y eventos anidados pueden ser miembros de una interfaz. Es posible que los métodos y propiedades no tengan un cuerpo. Los miembros de la interfaz son implícitamente Public y es posible que no especifiquen un modificador de acceso. El ámbito de un miembro declarado en una interfaz es el cuerpo de la interfaz en el que se produce la declaración, además de la lista de restricciones de esa interfaz (si es genérica y tiene restricciones).

Matrices

Una matriz es un tipo de referencia que contiene variables a las que se accede a través de índices correspondientes de una a una manera con el orden de las variables de la matriz. Las variables contenidas en una matriz, también denominadas elementos de la matriz, deben ser del mismo tipo y este tipo se denomina tipo de elemento de la matriz.

ArrayTypeName
    : NonArrayTypeName ArrayTypeModifiers
    ;

ArrayTypeModifiers
    : ArrayTypeModifier+
    ;

ArrayTypeModifier
    : OpenParenthesis RankList? CloseParenthesis
    ;

RankList
    : Comma*
    ;

ArrayNameModifier
    : ArrayTypeModifiers
    | ArraySizeInitializationModifier
    ;

Los elementos de una matriz entran en existencia cuando se crea una instancia de matriz y dejan de existir cuando se destruye la instancia de matriz. Cada elemento de una matriz se inicializa con el valor predeterminado de su tipo. El tipo System.Array es el tipo base de todos los tipos de matriz y es posible que no se cree una instancia. Cada tipo de matriz hereda los miembros declarados por el System.Array tipo y se puede convertir en él (y Object). Un tipo de matriz unidimensional con elemento T también implementa las interfaces System.Collections.Generic.IList(Of T) y IReadOnlyList(Of T); si T es un tipo de referencia, el tipo de matriz también implementa IList(Of U) y IReadOnlyList(Of U) para cualquier U que tenga una conversión de referencia de ampliación de T.

Una matriz tiene una clasificación que determina el número de índices asociados a cada elemento de matriz. La clasificación de una matriz determina el número de dimensiones de la matriz. Por ejemplo, una matriz con un rango de uno se denomina matriz unidimensional y una matriz con una clasificación mayor que una se denomina matriz multidimensional.

En el ejemplo siguiente se crea una matriz unidimensional de valores enteros, se inicializan los elementos de matriz y, a continuación, se imprime cada una de ellas:

Module Test
    Sub Main()
        Dim arr(5) As Integer
        Dim i As Integer

        For i = 0 To arr.Length - 1
            arr(i) = i * i
        Next i

        For i = 0 To arr.Length - 1
            Console.WriteLine("arr(" & i & ") = " & arr(i))
        Next i
    End Sub
End Module

El programa genera lo siguiente:

arr(0) = 0
arr(1) = 1
arr(2) = 4
arr(3) = 9
arr(4) = 16
arr(5) = 25

Cada dimensión de una matriz tiene una longitud asociada. Las longitudes de dimensión no forman parte del tipo de la matriz, sino que se establecen cuando se crea una instancia del tipo de matriz en tiempo de ejecución. La longitud de una dimensión determina el intervalo válido de índices para esa dimensión: para una dimensión de longitud N, los índices pueden oscilar entre cero y N-1. Si una dimensión es de longitud cero, no hay índices válidos para esa dimensión. El número total de elementos de una matriz es el producto de las longitudes de cada dimensión de la matriz. Si alguna de las dimensiones de una matriz tiene una longitud de cero, se dice que la matriz está vacía. El tipo de elemento de una matriz puede ser cualquier tipo.

Los tipos de matriz se especifican agregando un modificador a un nombre de tipo existente. El modificador consta de paréntesis izquierdo, un conjunto de cero o más comas y un paréntesis derecho. El tipo modificado es el tipo de elemento de la matriz y el número de dimensiones es el número de comas más uno. Si se especifica más de un modificador, el tipo de elemento de la matriz es una matriz. Los modificadores se leen de izquierda a derecha, con el modificador más a la izquierda siendo la matriz más externa. En el ejemplo

Module Test
    Dim arr As Integer(,)(,,)()
End Module

el tipo de elemento de arr es una matriz bidimensional de matrices tridimensionales de matrices unidimensionales de Integer.

Una variable también se puede declarar como de un tipo de matriz colocando un modificador de tipo de matriz o un modificador de inicialización de tamaño de matriz en el nombre de la variable. En ese caso, el tipo de elemento de matriz es el tipo especificado en la declaración y las dimensiones de matriz se determinan mediante el modificador de nombre de variable. Para mayor claridad, no es válido tener un modificador de tipo de matriz en un nombre de variable y un nombre de tipo en la misma declaración.

En el ejemplo siguiente se muestra una variedad de declaraciones de variables locales que usan tipos de matriz con Integer como tipo de elemento:

Module Test
    Sub Main()
        Dim a1() As Integer    ' Declares 1-dimensional array of integers.
        Dim a2(,) As Integer   ' Declares 2-dimensional array of integers.
        Dim a3(,,) As Integer  ' Declares 3-dimensional array of integers.

        Dim a4 As Integer()    ' Declares 1-dimensional array of integers.
        Dim a5 As Integer(,)   ' Declares 2-dimensional array of integers.
        Dim a6 As Integer(,,)  ' Declares 3-dimensional array of integers.

        ' Declare 1-dimensional array of 2-dimensional arrays of integers 
        Dim a7()(,) As Integer
        ' Declare 2-dimensional array of 1-dimensional arrays of integers.
        Dim a8(,)() As Integer

        Dim a9() As Integer() ' Not allowed.
    End Sub
End Module

Un modificador de nombre de tipo de matriz se extiende a todos los conjuntos de paréntesis que lo siguen. Esto significa que en las situaciones en las que se permite un conjunto de argumentos entre paréntesis después de un nombre de tipo, no es posible especificar los argumentos para un nombre de tipo de matriz. Por ejemplo:

Module Test
    Sub Main()
        ' This calls the Integer constructor.
        Dim x As New Integer(3)

        ' This declares a variable of Integer().
        Dim y As Integer()

        ' This gives an error.
        ' Array sizes can not be specified in a type name.
        Dim z As Integer()(3)
    End Sub
End Module

En el último caso, (3) se interpreta como parte del nombre de tipo en lugar de como un conjunto de argumentos de constructor.

Delegados

Un delegado es un tipo de referencia que hace referencia a un Shared método de un tipo o a un método de instancia de un objeto .

DelegateDeclaration
    : Attributes? TypeModifier* 'Delegate' MethodSignature StatementTerminator
    ;

MethodSignature
    : SubSignature
    | FunctionSignature
    ;

El equivalente más cercano de un delegado en otros lenguajes es un puntero de función, pero mientras que un puntero de función solo puede hacer referencia a Shared funciones, un delegado puede hacer referencia a métodos de Shared instancia y . En este último caso, el delegado almacena no solo una referencia al punto de entrada del método, sino también una referencia a la instancia de objeto con la que se va a invocar el método.

Es posible que la declaración de delegado no tenga una Handles cláusula, una Implements cláusula, un cuerpo del método o una End construcción. Es posible que la lista de parámetros de la declaración de delegado no contenga Optional parámetros o ParamArray . El dominio de accesibilidad del tipo de valor devuelto y los tipos de parámetro debe ser el mismo que o un superconjunto del dominio de accesibilidad del propio delegado.

Los miembros de un delegado son los miembros heredados de la clase System.Delegate. Un delegado también define los métodos siguientes:

  • Constructor que toma dos parámetros, uno de tipo Object y uno de tipo System.IntPtr.

  • Método Invoke que tiene la misma firma que el delegado.

  • Método BeginInvoke cuya firma es la firma del delegado, con tres diferencias. En primer lugar, el tipo de valor devuelto se cambia a System.IAsyncResult. En segundo lugar, se agregan dos parámetros adicionales al final de la lista de parámetros: el primero de tipo System.AsyncCallback y el segundo de tipo Object. Y, por último, se cambian todos los ByRef parámetros para que sean ByVal.

  • Método EndInvoke cuyo tipo de valor devuelto es el mismo que el delegado. Los parámetros del método son solo los parámetros delegados exactamente que son ByRef parámetros, en el mismo orden en que se producen en la firma del delegado. Además de esos parámetros, hay un parámetro adicional de tipo System.IAsyncResult al final de la lista de parámetros.

Hay tres pasos para definir y usar delegados: declaración, creación de instancias e invocación.

Los delegados se declaran mediante la sintaxis de declaración de delegado. En el ejemplo siguiente se declara un delegado denominado SimpleDelegate que no toma argumentos:

Delegate Sub SimpleDelegate()

En el ejemplo siguiente se crea una SimpleDelegate instancia y, a continuación, se le llama inmediatamente:

Module Test
    Sub F()
        System.Console.WriteLine("Test.F")
    End Sub 

    Sub Main()
        Dim d As SimpleDelegate = AddressOf F
        d()
    End Sub 
End Module

No hay mucho punto al crear instancias de un delegado para un método y, a continuación, llamar inmediatamente a través del delegado, ya que sería más sencillo llamar al método directamente. Los delegados muestran su utilidad cuando se usa su anonimato. En el ejemplo siguiente se muestra un MultiCall método que llama repetidamente a una SimpleDelegate instancia:

Sub MultiCall(d As SimpleDelegate, count As Integer)
    Dim i As Integer

    For i = 0 To count - 1
        d()
    Next i
End Sub

No es importante para el MultiCall método cuál es el método SimpleDelegate de destino de , qué accesibilidad tiene este método o si el método es Shared o no. Todo lo que importa es que la firma del método de destino sea compatible con SimpleDelegate.

Tipos parciales

Las declaraciones de clase y estructura pueden ser declaraciones parciales . Una declaración parcial puede o no describir completamente el tipo declarado dentro de la declaración. En su lugar, la declaración del tipo se puede distribuir entre varias declaraciones parciales dentro del programa; Los tipos parciales no se pueden declarar a través de los límites del programa. Una declaración de tipo parcial especifica el Partial modificador en la declaración. A continuación, cualquier otra declaración del programa para un tipo con el mismo nombre completo se combinará junto con la declaración parcial en tiempo de compilación para formar una sola declaración de tipo. Por ejemplo, el código siguiente declara una sola clase Test con miembros Test.C1 y Test.C2.

a.vb:

Public Partial Class Test
    Public Sub S1()
    End Sub
End Class

b.vb:

Public Class Test
    Public Sub S2()
    End Sub
End Class

Al combinar declaraciones de tipo parcial, al menos una de las declaraciones debe tener un Partial modificador; de lo contrario, se producirá un error en tiempo de compilación.

Nota. Aunque es posible especificar Partial solo una declaración entre muchas declaraciones parciales, es mejor especificarla en todas las declaraciones parciales. En la situación en la que una declaración parcial es visible, pero una o varias declaraciones parciales están ocultas (como el caso de extender el código generado por herramientas), es aceptable dejar el Partial modificador fuera de la declaración visible, pero especificarlo en las declaraciones ocultas.

Solo se pueden declarar clases y estructuras mediante declaraciones parciales. La aridad de un tipo se considera cuando se comparan declaraciones parciales juntas: dos clases con el mismo nombre pero distintos números de parámetros de tipo no se consideran declaraciones parciales del mismo tiempo. Las declaraciones parciales pueden especificar atributos, modificadores de clase, Inherits instrucción o Implements instrucción. En tiempo de compilación, todas las partes de las declaraciones parciales se combinan y se usan como parte de la declaración de tipo. Si hay conflictos entre atributos, modificadores, bases, interfaces o miembros de tipo, se produce un error en tiempo de compilación. Por ejemplo:

Public Partial Class Test1
    Implements IDisposable
End Class

Class Test1
    Inherits Object
    Implements IComparable
End Class

Public Partial Class Test2
End Class

Private Partial Class Test2
End Class

En el ejemplo anterior se declara un tipo que es Public, hereda de Object e implementa System.IDisposable y System.IComparable.Test1 Las declaraciones parciales de provocarán un error en tiempo de Test2 compilación porque una de las declaraciones dice que Test2 es Public y otra dice que Test2 es Private.

Los tipos parciales con parámetros de tipo pueden declarar restricciones y varianza para los parámetros de tipo, pero las restricciones y la varianza de cada declaración parcial deben coincidir. Por lo tanto, las restricciones y la varianza son especiales en que no se combinan automáticamente como otros modificadores:

Partial Public Class List(Of T As IEnumerable)
End Class

' Error: Constraints on T don't match
Class List(Of T As IComparable)
End Class

El hecho de que un tipo se declara mediante varias declaraciones parciales no afecta a las reglas de búsqueda de nombres dentro del tipo . Como resultado, una declaración de tipo parcial puede usar miembros declarados en otras declaraciones de tipo parcial o pueden implementar métodos en interfaces declaradas en otras declaraciones de tipo parcial. Por ejemplo:

Public Partial Class Test1
    Implements IDisposable

    Private IsDisposed As Boolean = False
End Class

Class Test1
    Private Sub Dispose() Implements IDisposable.Dispose
        If Not IsDisposed Then
            ...
        End If
    End Sub
End Class

Los tipos anidados también pueden tener declaraciones parciales. Por ejemplo:

Public Partial Class Test
    Public Partial Class NestedTest
        Public Sub S1()
        End Sub
    End Class
End Class

Public Partial Class Test
    Public Partial Class NestedTest
        Public Sub S2()
        End Sub
    End Class
End Class

Los inicializadores dentro de una declaración parcial se seguirán ejecutando en orden de declaración; sin embargo, no hay ningún orden garantizado de ejecución para inicializadores que se producen en declaraciones parciales independientes.

Tipos construidos

Una declaración de tipo genérico, por sí misma, no denota un tipo. En su lugar, se puede usar una declaración de tipo genérico como "plano técnico" para formar muchos tipos diferentes aplicando argumentos de tipo. Un tipo genérico que tiene argumentos de tipo aplicados se denomina tipo construido. Los argumentos de tipo de un tipo construido siempre deben satisfacer las restricciones colocadas en los parámetros de tipo a los que coinciden.

Un nombre de tipo podría identificar un tipo construido aunque no especifique directamente parámetros de tipo. Esto puede ocurrir cuando un tipo está anidado dentro de una declaración de clase genérica y el tipo de instancia de la declaración contenedora se usa implícitamente para la búsqueda de nombres:

Class Outer(Of T) 
    Public Class Inner 
    End Class

    ' Type of i is the constructed type Outer(Of T).Inner
    Public i As Inner 
End Class

Se puede acceder a un tipo C(Of T1,...,Tn) construido cuando se puede acceder al tipo genérico y a todos los argumentos de tipo. Por ejemplo, si el tipo C genérico es Public y todos los argumentos T1,...,Tn de tipo son Public, el tipo construido es Public. Si el nombre de tipo o uno de los argumentos de tipo es Private, sin embargo, la accesibilidad del tipo construido es Private. Si un argumento de tipo del tipo construido es Protected y otro argumento de tipo es Friend, el tipo construido solo es accesible en la clase y sus subclases de este ensamblado o en cualquier ensamblado al que se le haya concedido Friend acceso. En otras palabras, el dominio de accesibilidad de un tipo construido es la intersección de los dominios de accesibilidad de sus partes constituyentes.

Nota. El hecho de que el dominio de accesibilidad del tipo construido es la intersección de sus partes constituidas tiene el efecto secundario interesante de definir un nuevo nivel de accesibilidad. Tipo construido que contiene un elemento que es Protected y un elemento al que solo se Friend puede tener acceso en contextos que pueden tener acceso a ambosFriendmiembros yProtected . Sin embargo, no hay ninguna manera de expresar este nivel de accesibilidad en el lenguaje, ya que la accesibilidad Protected Friend significa que se puede tener acceso a una entidad en un contexto que pueda acceder a cualquiera deFriendlos miembros oProtected .

Las interfaces base, implementadas y los miembros de los tipos construidos se determinan sustituyendo los argumentos de tipo proporcionados por cada aparición del parámetro de tipo en el tipo genérico.

Tipos abiertos y tipos cerrados

Un tipo construido para quién o varios argumentos de tipo son parámetros de tipo de un tipo contenedor o método se denomina tipo abierto. Esto se debe a que algunos de los parámetros de tipo del tipo todavía no se conocen, por lo que la forma real del tipo aún no se conoce completamente. Por el contrario, un tipo genérico cuyos argumentos de tipo son todos los parámetros que no son de tipo se denomina tipo cerrado. La forma de un tipo cerrado siempre es totalmente conocida. Por ejemplo:

Class Base(Of T, V)
End Class

Class Derived(Of V)
    Inherits Base(Of Integer, V)
End Class

Class MoreDerived
    Inherits Derived(Of Double)
End Class

El tipo Base(Of Integer, V) construido es un tipo abierto porque, aunque se ha proporcionado el parámetro T type, el parámetro U type se ha proporcionado otro parámetro de tipo. Por lo tanto, la forma completa del tipo aún no se conoce. Sin embargo, el tipo Derived(Of Double)construido es un tipo cerrado porque se han proporcionado todos los parámetros de tipo de la jerarquía de herencia.

Los tipos abiertos se definen de la manera siguiente:

  • Un parámetro de tipo es un tipo abierto.

  • Un tipo de matriz es un tipo abierto si su tipo de elemento es un tipo abierto.

  • Un tipo construido es un tipo abierto si uno o varios de sus argumentos de tipo son un tipo abierto.

  • Un tipo cerrado es un tipo que no es un tipo abierto.

Dado que el punto de entrada del programa no puede estar en un tipo genérico, todos los tipos usados en tiempo de ejecución serán tipos cerrados.

Tipos especiales

.NET Framework contiene una serie de clases que son tratadas especialmente por .NET Framework y por el lenguaje de Visual Basic:

El tipo System.Void, que representa un tipo void en .NET Framework, solo se puede hacer referencia directamente en GetType expresiones.

Los tipos System.RuntimeArgumentHandle, System.ArgIterator y System.TypedReference todos pueden contener punteros a la pila, por lo que no pueden aparecer en el montón de .NET Framework. Por lo tanto, no se pueden usar como tipos de elemento de matriz, tipos devueltos, tipos de campo, argumentos de tipo genérico, tipos que aceptan valores NULL, ByRef tipos de parámetros, el tipo de un valor que se convierte en Object o System.ValueType, el destino de una llamada a los miembros de instancia de Object o System.ValueType, o se levanta en un cierre.