Condividi tramite


Tipi in Visual Basic

Le due categorie fondamentali di tipi in Visual Basic sono tipi valore e tipi riferimento. I tipi primitivi (ad eccezione delle stringhe), le enumerazioni e le strutture sono tipi valore. Classi, stringhe, moduli standard, interfacce, matrici e delegati sono tipi di riferimento.

Ogni tipo ha un valore predefinito, ovvero il valore assegnato alle variabili di quel tipo al momento dell'inizializzazione.

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?
    ;

Tipi Valore e Tipi di Riferimento

Anche se i tipi valore e i tipi riferimento possono essere simili in termini di sintassi e utilizzo della dichiarazione, la semantica è distinta.

I tipi di riferimento vengono archiviati nell'heap di runtime; è possibile accedervi solo tramite un riferimento a tale archiviazione. Poiché i tipi di riferimento sono sempre accessibili tramite riferimenti, la loro durata viene gestita da .NET Framework. I riferimenti in sospeso a una determinata istanza vengono rilevati e l'istanza viene eliminata definitivamente solo quando non rimangono altri riferimenti. Una variabile di tipo reference contiene un riferimento a un valore di tale tipo, un valore di un tipo più derivato o un valore Null. Un valore Null si riferisce a nulla; non è possibile eseguire alcuna operazione con un valore Null ad eccezione dell'assegnazione. L'assegnazione a una variabile di un tipo riferimento crea una copia del riferimento anziché una copia del valore a cui si fa riferimento. Per una variabile di un tipo riferimento, il valore predefinito è un valore Null.

I tipi valore vengono archiviati direttamente nello stack, all'interno di una matrice o all'interno di un altro tipo; è possibile accedere direttamente al loro spazio di archiviazione. Poiché i tipi di valore vengono archiviati direttamente all'interno delle variabili, la durata viene determinata dalla durata della variabile che li contiene. Quando la posizione contenente un'istanza di tipo valore viene eliminata definitivamente, viene eliminata anche l'istanza del tipo di valore. I tipi valore sono sempre accessibili direttamente; non è possibile creare un riferimento a un tipo di valore. La proibizione di un riferimento di questo tipo rende impossibile fare riferimento a un'istanza della classe valore che è stata eliminata definitivamente. Poiché i tipi valore sono sempre NotInheritable, una variabile di un tipo valore contiene sempre un valore di tale tipo. Per questo motivo, il valore di un tipo valore non può essere un valore Null, né può fare riferimento a un oggetto di un tipo più derivato. L'assegnazione a una variabile di un tipo valore crea una copia del valore assegnato. Per una variabile di un tipo valore, il valore predefinito è il risultato dell'inizializzazione di ogni membro variabile del tipo sul valore predefinito.

L'esempio seguente mostra la differenza tra i tipi riferimento e i tipi valore:

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

L'output del programma è:

Values: 0, 123
Refs: 123, 123

L'assegnazione alla variabile locale non influisce val2 sulla variabile locale perché entrambe val1 le variabili locali sono di un tipo valore (il tipo Integer) e ogni variabile locale di un tipo valore ha una propria risorsa di archiviazione. Al contrario, l'assegnazione ref2.Value = 123; influisce sull'oggetto che ref1 fa riferimento e ref2 .

Una cosa da notare sul sistema di tipi .NET Framework è che anche se le strutture, le enumerazioni e i tipi primitivi (ad eccezione Stringdi ) sono tipi valore, ereditano tutti dai tipi riferimento. Le strutture e i tipi primitivi ereditano dal tipo riferimento System.ValueType, che eredita da Object. I tipi enumerati ereditano dal tipo riferimento System.Enum, che eredita da System.ValueType.

Tipi di valore nullable

Per i tipi valore, è possibile aggiungere un ? modificatore a un nome di tipo per rappresentare la versione nullable di tale tipo.

NullableTypeName
    : NonArrayTypeName '?'
    ;

NullableNameModifier
    : '?'
    ;

Un tipo valore nullable può contenere gli stessi valori della versione non nullable del tipo e del valore Null. Pertanto, per un tipo valore nullable, l'assegnazione Nothing a una variabile del tipo imposta il valore della variabile sul valore Null, non sul valore zero del tipo valore. Per esempio:

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 variabile può anche essere dichiarata come di un tipo valore nullable inserendo un modificatore di tipo nullable nel nome della variabile. Per maggiore chiarezza, non è valido disporre di un modificatore di tipo nullable sia in un nome di variabile che in un nome di tipo nella stessa dichiarazione. Poiché i tipi nullable vengono implementati usando il tipo System.Nullable(Of T), il tipo T? è sinonimo del tipo System.Nullable(Of T)e i due nomi possono essere usati in modo intercambiabile. Il ? modificatore non può essere inserito in un tipo già nullable, pertanto non è possibile dichiarare il tipo Integer?? o System.Nullable(Of Integer)?.

Un tipo di T? valore nullable include i membri di System.Nullable(Of T) , nonché qualsiasi operatore o conversione lifted dal tipo sottostante al tipo TT?. Il lifting di operatori e conversioni dal tipo sottostante, nella maggior parte dei casi sostituendo i tipi valore nullable per i tipi valore non nullable. Ciò consente anche molte delle stesse conversioni e operazioni che si applicano a T da applicare.T?

Implementazione dell'interfaccia

Le dichiarazioni di struttura e classe possono dichiarare di implementare un set di tipi di interfaccia tramite una o più Implements clausole.

TypeImplementsClause
    : 'Implements' TypeImplements StatementTerminator
    ;

TypeImplements
    : NonArrayTypeName ( Comma NonArrayTypeName )*
    ;

Tutti i tipi specificati nella Implements clausola devono essere interfacce e il tipo deve implementare tutti i membri delle interfacce. Per esempio:

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 che implementa un'interfaccia implementa anche in modo implicito tutte le interfacce di base dell'interfaccia. Questo vale anche se il tipo non elenca in modo esplicito tutte le interfacce di base nella Implements clausola . In questo esempio la TextBox struttura implementa sia IControl che ITextBox.

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

Dichiarando che un tipo implementa un'interfaccia in e di se stessa, non dichiara nulla nello spazio di dichiarazione del tipo. Pertanto, è valido implementare due interfacce con un metodo con lo stesso nome.

I tipi non possono implementare un parametro di tipo autonomamente, anche se può coinvolgere i parametri di tipo inclusi nell'ambito.

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

    ...
End Class

Le interfacce generiche possono essere implementate più volte usando argomenti di tipo diversi. Tuttavia, un tipo generico non può implementare un'interfaccia generica usando un parametro di tipo se il parametro di tipo fornito (indipendentemente dai vincoli di tipo) potrebbe sovrapporsi a un'altra implementazione di tale interfaccia. Per esempio:

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

Tipi primitivi

I tipi primitivi vengono identificati tramite parole chiave, ovvero alias per i tipi predefiniti nello spazio dei System nomi. Un tipo primitivo è completamente indistinguibile dal tipo che alias: la scrittura della parola Byte riservata equivale esattamente alla scrittura System.Bytedi . I tipi primitivi sono noti anche come tipi intrinseci.

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

NumericTypeName
    : IntegralTypeName
    | FloatingPointTypeName
    | 'Decimal'
    ;

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

FloatingPointTypeName
    : 'Single' | 'Double'
    ;

Poiché un tipo primitivo esegue l'alias di un tipo regolare, ogni tipo primitivo ha membri. Ad esempio, Integer ha i membri dichiarati in System.Int32. I valori letterali possono essere considerati come istanze dei tipi corrispondenti.

  • I tipi primitivi differiscono da altri tipi di struttura in quanto consentono determinate operazioni aggiuntive:

  • I tipi primitivi consentono di creare valori scrivendo valori letterali. Ad esempio, 123I è un valore letterale di tipo Integer.

  • È possibile dichiarare costanti dei tipi primitivi.

  • Quando gli operandi di un'espressione sono tutte costanti di tipo primitivo, è possibile che il compilatore valuti l'espressione in fase di compilazione. Tale espressione è nota come espressione costante.

Visual Basic definisce i tipi primitivi seguenti:

  • I tipi Byte di valore integrale (intero senza segno a 1 byte), SByte (intero con segno a 1 byte), (intero senza segno a 2 byte), ShortUShort (intero con segno a 2 byte), UInteger (intero senza segno a 4 byte), (intero con segno a 4 byte), Integer (intero senza segno a 8 byte) ULong e Long (intero con segno a 8 byte). Questi tipi eseguono il mapping rispettivamente a System.Byte, System.Int32System.Int16System.UInt16System.UInt32System.SByteSystem.UInt64 e .System.Int64 Il valore predefinito di un tipo integrale equivale al valore letterale 0.

  • I tipi di Single valore a virgola mobile (a virgola mobile a 4 byte) e Double (virgola mobile a 8 byte). Questi tipi eseguono rispettivamente il mapping a System.Single e System.Double. Il valore predefinito di un tipo a virgola mobile equivale al valore letterale 0.

  • Tipo Decimal (valore decimale a 16 byte), che esegue il mapping a System.Decimal. Il valore predefinito di decimal è equivalente al valore letterale 0D.

  • Tipo Boolean di valore, che rappresenta un valore di verità, in genere il risultato di un'operazione relazionale o logica. Il valore letterale è di tipo System.Boolean. Il valore predefinito del Boolean tipo equivale al valore letterale False.

  • Tipo Date di valore, che rappresenta una data e/o un'ora ed esegue il mapping a System.DateTime. Il valore predefinito del Date tipo equivale al valore letterale # 01/01/0001 12:00:00AM #.

  • Tipo Char di valore, che rappresenta un singolo carattere Unicode ed esegue il mapping a System.Char. Il valore predefinito del Char tipo equivale all'espressione ChrW(0)costante .

  • Tipo String di riferimento, che rappresenta una sequenza di caratteri Unicode ed esegue il mapping a System.String. Il valore predefinito del String tipo è un valore Null.

Enumerazioni

Le enumerazioni sono tipi valore che ereditano da System.Enum e rappresentano simbolicamente un set di valori di uno dei tipi integrali primitivi.

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

Per un tipo di Eenumerazione , il valore predefinito è il valore prodotto dall'espressione CType(0, E).

Il tipo sottostante di un'enumerazione deve essere un tipo integrale che può rappresentare tutti i valori dell'enumeratore definiti nell'enumerazione . Se viene specificato un tipo sottostante, deve essere Byte, , UShortShortSByte, ULongIntegerUInteger, Long, o uno dei tipi corrispondenti nello spazio dei System nomi . Se non viene specificato in modo esplicito alcun tipo sottostante, il valore predefinito è Integer.

Nell'esempio seguente viene dichiarata un'enumerazione con un tipo sottostante di Long:

Enum Color As Long
    Red
    Green
    Blue
End Enum

Uno sviluppatore potrebbe scegliere di usare un tipo sottostante di Long, come nell'esempio, per abilitare l'uso di valori compresi nell'intervallo di Long, ma non nell'intervallo di Integero per mantenere questa opzione per il futuro.

Membri dell'enumerazione

I membri di un'enumerazione sono i valori enumerati dichiarati nell'enumerazione e i membri ereditati dalla classe System.Enum.

L'ambito di un membro di enumerazione è il corpo della dichiarazione di enumerazione. Ciò significa che all'esterno di una dichiarazione di enumerazione, un membro di enumerazione deve essere sempre qualificato (a meno che il tipo non venga importato in modo specifico in uno spazio dei nomi tramite un'importazione dello spazio dei nomi).

L'ordine di dichiarazione per le dichiarazioni dei membri di enumerazione è significativo quando i valori delle espressioni costanti vengono omessi. I membri di enumerazione hanno Public accesso solo in modo implicito. Non sono consentiti modificatori di accesso nelle dichiarazioni dei membri di enumerazione.

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

Valori di enumerazione

I valori enumerati in un elenco di membri di enumerazione vengono dichiarati come costanti tipate come tipo di enumerazione sottostante e possono essere visualizzati ovunque siano necessarie costanti. Una definizione di membro di enumerazione con = assegna al membro associato il valore indicato dall'espressione costante. L'espressione costante deve restituire un tipo integrale convertibile in modo implicito nel tipo sottostante e deve essere compreso nell'intervallo di valori che possono essere rappresentati dal tipo sottostante. L'esempio seguente è in errore perché i valori 1.5costanti , 2.3e 3.3 non sono convertibili in modo implicito nel tipo Long integrale sottostante con semantica strict.

Option Strict On

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

Più membri di enumerazione possono condividere lo stesso valore associato, come illustrato di seguito:

Enum Color
    Red
    Green
    Blue
    Max = Blue
End Enum

L'esempio mostra un'enumerazione con due membri di enumerazione , Blue e Max , che hanno lo stesso valore associato.

Se la prima definizione del valore dell'enumeratore nell'enumerazione non dispone di inizializzatore, il valore della costante corrispondente è 0. Una definizione di valore di enumerazione senza un inizializzatore fornisce all'enumeratore il valore ottenuto aumentando il valore del valore di enumerazione precedente da 1. Questo valore aumentato deve essere compreso nell'intervallo di valori che possono essere rappresentati dal tipo sottostante.

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

Nell'esempio precedente vengono stampati i valori di enumerazione e i relativi valori associati. L'output è il seguente:

Red = 0
Green = 10
Blue = 11

I motivi per i valori sono i seguenti:

  • Il valore Red di enumerazione viene assegnato automaticamente al valore 0 (poiché non ha inizializzatore ed è il primo membro del valore di enumerazione).

  • Il valore Green di enumerazione viene assegnato in modo esplicito al valore 10.

  • Al valore Blue di enumerazione viene assegnato automaticamente il valore maggiore del valore di enumerazione che lo precede a livello di testo.

L'espressione costante non può usare direttamente o indirettamente il valore del proprio valore di enumerazione associato, ovvero la circolarità nell'espressione costante non è consentita. L'esempio seguente non è valido perché le dichiarazioni di A e B sono circolari.

Enum Circular
    A = B
    B
End Enum

A B dipende in modo esplicito e B dipende in A modo implicito.

Classi

Una classe è una struttura di dati che può contenere membri dati (costanti, variabili ed eventi), membri di funzione (metodi, proprietà, indicizzatori, operatori e costruttori) e tipi annidati. Le classi sono tipi di riferimento.

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

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

L'esempio seguente mostra una classe che contiene ogni tipo di membro:

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

L'esempio seguente mostra gli usi di questi membri:

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

Esistono due modificatori specifici della classe e MustInheritNotInheritable. Non è valido specificarli entrambi.

Specifica di base della classe

Una dichiarazione di classe può includere una specifica del tipo di base che definisce il tipo di base diretto della classe .

ClassBase
    : 'Inherits' NonArrayTypeName StatementTerminator
    ;

Se una dichiarazione di classe non ha alcun tipo di base esplicito, il tipo di base diretto è implicitamente Object. Per esempio:

Class Base
End Class

Class Derived
    Inherits Base
End Class

I tipi di base non possono essere un parametro di tipo autonomamente, anche se può includere i parametri di tipo inclusi nell'ambito.

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

Le classi possono derivare solo da Object classi e . Non è valido per una classe da derivare da System.ValueType, System.Enum, System.MulticastDelegateSystem.Arrayo System.Delegate. Una classe generica non può derivare da System.Attribute o da una classe che ne deriva.

Ogni classe ha esattamente una classe base diretta e la circolarità nella derivazione è vietata. Non è possibile derivare da una NotInheritable classe e il dominio di accessibilità della classe base deve essere uguale o un superset del dominio di accessibilità della classe stessa.

Membri della classe

I membri di una classe sono costituiti dai membri introdotti dalle dichiarazioni dei membri della classe e dai membri ereditati dalla relativa classe base diretta.

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

Una dichiarazione di membro di classe può avere Publicaccesso , ProtectedFriend, , Protected Friend, o Private . Quando una dichiarazione di membro di classe non include un modificatore di accesso, la dichiarazione viene impostata per impostazione predefinita per Public l'accesso, a meno che non si tratti di una dichiarazione di variabile. In tal caso, per impostazione predefinita viene eseguito l'accesso Private .

L'ambito di un membro della classe è il corpo della classe in cui si verifica la dichiarazione del membro, oltre all'elenco di vincoli di tale classe (se è generico e ha vincoli). Se il membro ha Friend accesso, il relativo ambito si estende al corpo della classe di qualsiasi classe derivata nello stesso programma o a qualsiasi assembly a cui è stato concesso Friend l'accesso e, se il membro ha Public, Protectedo Protected Friend l'accesso, il relativo ambito si estende al corpo della classe di qualsiasi classe derivata in qualsiasi programma.

Structures

Le strutture sono tipi valore che ereditano da System.ValueType. Le strutture sono simili alle classi in cui rappresentano strutture di dati che possono contenere membri dati e membri di funzione. A differenza delle classi, tuttavia, le strutture non richiedono l'allocazione dell'heap.

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

StructureModifier
    : TypeModifier
    | 'Partial'
    ;

Nel caso delle classi, è possibile che due variabili facciano riferimento allo stesso oggetto e quindi sia possibile che le operazioni su una variabile influiscano sull'oggetto a cui fa riferimento l'altra variabile. Con le strutture, ognuna delle variabili ha una propria copia deiShared dati non, pertanto non è possibile che le operazioni su uno influiscano sull'altra, come illustrato nell'esempio seguente:

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

Data la dichiarazione precedente, il codice seguente restituisce il valore 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

L'assegnazione di a a b crea una copia del valore e b pertanto non è interessata dall'assegnazione a a.x. Se Point fosse invece stato dichiarato come classe, l'output sarebbe 100 perché a e b farebbero riferimento allo stesso oggetto.

Membri della struttura

I membri di una struttura sono i membri introdotti dalle dichiarazioni dei membri della struttura e dai membri ereditati da System.ValueType.

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

Ogni struttura ha in modo implicito un Public costruttore di istanza senza parametri che produce il valore predefinito della struttura. Di conseguenza, non è possibile che una dichiarazione di tipo struttura dichiari un costruttore di istanza senza parametri. Un tipo di struttura è tuttavia autorizzato a dichiarare costruttori di istanze con parametri , come nell'esempio seguente:

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

Data la dichiarazione precedente, le istruzioni seguenti creano un Point oggetto con x e y inizializzato su zero.

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

Poiché le strutture contengono direttamente i valori dei campi anziché i riferimenti a tali valori, le strutture non possono contenere campi che si fanno riferimento direttamente o indirettamente. Ad esempio, il codice seguente non è valido:

Structure S1
    Dim f1 As S2
End Structure

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

In genere, una dichiarazione di membro della struttura può avere Publicaccesso solo a , Friendo Private , ma quando si esegue l'override dei membri ereditati da ObjectProtected e Protected Friend l'accesso può essere usato anche. Quando una dichiarazione di membro della struttura non include un modificatore di accesso, la dichiarazione viene impostata per impostazione predefinita per Public l'accesso. L'ambito di un membro dichiarato da una struttura è il corpo della struttura in cui si verifica la dichiarazione, oltre ai vincoli di tale struttura (se era generico e aveva vincoli).

Moduli standard

Un modulo standard è un tipo i cui membri sono implicitamente Shared e con ambito allo spazio dei nomi contenitore del modulo standard, anziché solo alla dichiarazione del modulo standard stessa. È possibile che non venga mai creata un'istanza dei moduli standard. Si tratta di un errore per dichiarare una variabile di un tipo di modulo standard.

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

Un membro di un modulo standard ha due nomi completi, uno senza il nome del modulo standard e uno con il nome del modulo standard. Più moduli standard in uno spazio dei nomi possono definire un membro con un nome specifico; I riferimenti non qualificati al nome all'esterno di uno dei due moduli sono ambigui. Per esempio:

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 modulo può essere dichiarato solo in uno spazio dei nomi e potrebbe non essere annidato in un altro tipo. I moduli standard potrebbero non implementare interfacce, derivano in modo implicito da Objecte hanno solo Shared costruttori.

Membri del modulo Standard

I membri di un modulo standard sono i membri introdotti dalle relative dichiarazioni di membro e dai membri ereditati da Object. I moduli standard possono avere qualsiasi tipo di membro, ad eccezione dei costruttori di istanza. Tutti i membri del tipo di modulo standard sono implicitamente Shared.

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

In genere, una dichiarazione di membro del modulo standard può avere Publicaccesso solo a , Friendo Private , ma quando si esegue l'override dei membri ereditati da Object, è possibile specificare i Protected modificatori di accesso e Protected Friend . Quando una dichiarazione di membro del modulo standard non include un modificatore di accesso, la dichiarazione viene impostata per impostazione predefinita per Public l'accesso, a meno che non si tratti di una variabile, che per impostazione predefinita ha Private accesso.

Come indicato in precedenza, l'ambito di un membro del modulo standard è la dichiarazione contenente la dichiarazione del modulo standard. I membri ereditati da Object non sono inclusi in questo ambito speciale. Tali membri non hanno ambito e devono essere sempre qualificati con il nome del modulo. Se il membro ha Friend accesso, il relativo ambito si estende solo ai membri dello spazio dei nomi dichiarati nello stesso programma o assembly a cui è stato concesso Friend l'accesso.

Interfacce

Le interfacce sono tipi di riferimento implementati da altri tipi per garantire che supportino determinati metodi. Un'interfaccia non viene mai creata direttamente e non ha alcuna rappresentazione effettiva. Gli altri tipi devono essere convertiti in un tipo di interfaccia. Un'interfaccia definisce un contratto. Una classe o una struttura che implementa un'interfaccia deve rispettare il contratto.

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

Nell'esempio seguente viene illustrata un'interfaccia che contiene una proprietà Itempredefinita , un evento E, un metodo Fe una proprietà P:

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

    Event E()

    Sub F(value As Integer)

    Property P() As String
End Interface

Le interfacce possono usare più ereditarietà. Nell'esempio seguente l'interfaccia IComboBox eredita sia da che IListBoxda ITextBox :

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

Le classi e le strutture possono implementare più interfacce. Nell'esempio seguente la classe EditBox deriva dalla classe Control e implementa sia IControl che IDataBound:

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

Ereditarietà dell'interfaccia

Le interfacce di base di un'interfaccia sono le interfacce di base esplicite e le relative interfacce di base. In altre parole, il set di interfacce di base è la chiusura transitiva completa delle interfacce di base esplicite, le relative interfacce di base esplicite e così via. Se una dichiarazione di interfaccia non ha una base di interfaccia esplicita, non esiste un'interfaccia di base per il tipo - le interfacce non ereditano da Object (anche se hanno una conversione verso un tipo più grande in Object).

InterfaceBase
    : 'Inherits' InterfaceBases StatementTerminator
    ;

InterfaceBases
    : NonArrayTypeName ( Comma NonArrayTypeName )*
    ;

Nell'esempio seguente le interfacce di base di IComboBox sono IControl, ITextBoxe 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

Un'interfaccia eredita tutti i membri delle relative interfacce di base. In altre parole, l'interfaccia IComboBox precedente eredita i membri SetText e SetItemsPaint.

Una classe o una struttura che implementa un'interfaccia implementa in modo implicito anche tutte le interfacce di base dell'interfaccia.

Se un'interfaccia viene visualizzata più volte nella chiusura transitiva delle interfacce di base, contribuisce solo i membri all'interfaccia derivata una volta. Un tipo che implementa l'interfaccia derivata deve implementare solo i metodi dell'interfaccia di base definita moltiplicata una sola volta. Nell'esempio seguente è Paint necessario implementare una sola volta, anche se la classe implementa IComboBox e 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 clausola non ha alcun effetto su altre Inherits clausole. Nell'esempio seguente, IDerived deve qualificare il nome di 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

Il dominio di accessibilità di un'interfaccia di base deve essere uguale a o a un superset del dominio di accessibilità dell'interfaccia stessa.

Membri dell'interfaccia

I membri di un'interfaccia sono costituiti dai membri introdotti dalle relative dichiarazioni di membro e dai membri ereditati dalle relative interfacce di base.

InterfaceMemberDeclaration
    : NonModuleDeclaration
    | InterfaceEventMemberDeclaration
    | InterfaceMethodMemberDeclaration
    | InterfacePropertyMemberDeclaration
    ;

Anche se le interfacce non ereditano membri da Object, perché ogni classe o struttura che implementa un'interfaccia eredita da Object, i membri di , inclusi i metodi di Objectestensione, sono considerati membri di un'interfaccia e possono essere chiamati direttamente su un'interfaccia senza richiedere un cast a Object. Per esempio:

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

Membri di un'interfaccia con lo stesso nome dei membri di Object membri shadow Object in modo implicito. Solo i tipi annidati, i metodi, le proprietà e gli eventi possono essere membri di un'interfaccia. I metodi e le proprietà potrebbero non avere un corpo. I membri dell'interfaccia sono implicitamente Public e potrebbero non specificare un modificatore di accesso. L'ambito di un membro dichiarato in un'interfaccia è il corpo dell'interfaccia in cui si verifica la dichiarazione, oltre all'elenco di vincoli di tale interfaccia (se è generico e ha vincoli).

Matrici

Una matrice è un tipo riferimento che contiene variabili accessibili tramite indici corrispondenti in modo uno-a-uno con l'ordine delle variabili nella matrice. Le variabili contenute in una matrice, denominate anche elementi della matrice, devono essere tutte dello stesso tipo e questo tipo è denominato tipo di elemento della matrice.

ArrayTypeName
    : NonArrayTypeName ArrayTypeModifiers
    ;

ArrayTypeModifiers
    : ArrayTypeModifier+
    ;

ArrayTypeModifier
    : OpenParenthesis RankList? CloseParenthesis
    ;

RankList
    : Comma*
    ;

ArrayNameModifier
    : ArrayTypeModifiers
    | ArraySizeInitializationModifier
    ;

Gli elementi di una matrice vengono creati quando viene creata un'istanza di matrice e cessano di esistere quando l'istanza della matrice viene eliminata definitivamente. Ogni elemento di una matrice viene inizializzato sul valore predefinito del relativo tipo. Il tipo System.Array è il tipo di base di tutti i tipi di matrice e potrebbe non essere creata un'istanza. Ogni tipo di matrice eredita i membri dichiarati dal System.Array tipo ed è convertibile in esso (e Object). Un tipo di matrice unidimensionale con elemento T implementa anche le interfacce System.Collections.Generic.IList(Of T) e IReadOnlyList(Of T); se T è un tipo riferimento, il tipo di matrice implementa IList(Of U) e IReadOnlyList(Of U) per qualsiasi U oggetto con una conversione di riferimento più ampia da T.

Una matrice ha un rango che determina il numero di indici associati a ogni elemento della matrice. Il rango di una matrice determina il numero di dimensioni della matrice. Ad esempio, una matrice con un rango di uno viene denominata matrice unidimensionale e una matrice con un rango maggiore di una è detta matrice multidimensionale.

L'esempio seguente crea una matrice unidimensionale di valori integer, inizializza gli elementi della matrice e quindi li stampa:

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

Il programma restituisce quanto segue:

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

Ogni dimensione di una matrice ha una lunghezza associata. Le lunghezze delle dimensioni non fanno parte del tipo della matrice, ma vengono invece stabilite quando viene creata un'istanza del tipo di matrice in fase di esecuzione. La lunghezza di una dimensione determina l'intervallo valido di indici per tale dimensione: per una dimensione di lunghezza N, gli indici possono variare da zero a N-1. Se una dimensione è di lunghezza zero, non sono presenti indici validi per tale dimensione. Il numero totale di elementi in una matrice è il prodotto delle lunghezze di ogni dimensione nella matrice. Se una delle dimensioni di una matrice ha una lunghezza pari a zero, si dice che la matrice sia vuota. Il tipo di elemento di una matrice può essere qualsiasi tipo.

I tipi di matrice vengono specificati aggiungendo un modificatore a un nome di tipo esistente. Il modificatore è costituito da una parentesi sinistra, da un set di zero o più virgole e da una parentesi destra. Il tipo modificato è il tipo di elemento della matrice e il numero di dimensioni è il numero di virgole più uno. Se viene specificato più di un modificatore, il tipo di elemento della matrice è una matrice. I modificatori vengono letti da sinistra a destra, con il modificatore più a sinistra che rappresenta la matrice più esterna. Nell'esempio

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

Il tipo di elemento di arr è una matrice bidimensionale di matrici tridimensionali di matrici unidimensionali di Integer.

Una variabile può anche essere dichiarata come di un tipo di matrice inserendo un modificatore di tipo matrice o un modificatore di inizializzazione delle dimensioni della matrice sul nome della variabile. In tal caso, il tipo di elemento matrice è il tipo specificato nella dichiarazione e le dimensioni della matrice sono determinate dal modificatore del nome della variabile. Per maggiore chiarezza, non è valido avere un modificatore di tipo matrice sia in un nome di variabile che in un nome di tipo nella stessa dichiarazione.

L'esempio seguente mostra un'ampia gamma di dichiarazioni di variabili locali che usano tipi di matrice con Integer come tipo di 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 modificatore del nome del tipo di matrice si estende a tutti i set di parentesi che lo seguono. Ciò significa che nelle situazioni in cui un set di argomenti racchiusi tra parentesi è consentito dopo un nome di tipo, non è possibile specificare gli argomenti per un nome di tipo matrice. Per esempio:

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

Nell'ultimo caso, (3) viene interpretato come parte del nome del tipo anziché come set di argomenti del costruttore.

Delegati

Un delegato è un tipo riferimento che fa riferimento a un Shared metodo di un tipo o a un metodo di istanza di un oggetto.

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

MethodSignature
    : SubSignature
    | FunctionSignature
    ;

L'equivalente più vicino di un delegato in altri linguaggi è un puntatore a funzione, ma mentre un puntatore a funzione può fare riferimento Shared solo a funzioni, un delegato può fare riferimento a Shared entrambi i metodi di istanza e . Nel secondo caso, il delegato archivia non solo un riferimento al punto di ingresso del metodo, ma anche un riferimento all'istanza dell'oggetto con cui richiamare il metodo.

La dichiarazione del delegato potrebbe non avere una clausola, una HandlesImplements clausola, un corpo del metodo o un End costrutto. L'elenco di parametri della dichiarazione del delegato potrebbe non contenere Optional parametri o ParamArray . Il dominio di accessibilità del tipo restituito e dei tipi di parametro deve essere uguale a o a un superset del dominio di accessibilità del delegato stesso.

I membri di un delegato sono i membri ereditati dalla classe System.Delegate. Un delegato definisce anche i metodi seguenti:

  • Costruttore che accetta due parametri, uno di tipo Object e uno di tipo System.IntPtr.

  • Metodo Invoke con la stessa firma del delegato.

  • Metodo BeginInvoke la cui firma è la firma del delegato, con tre differenze. Prima di tutto, il tipo restituito viene modificato in System.IAsyncResult. In secondo luogo, vengono aggiunti due parametri aggiuntivi alla fine dell'elenco di parametri: il primo di tipo e il secondo di tipo System.AsyncCallbackObject. Infine, tutti i ByRef parametri vengono modificati in .ByVal

  • Metodo EndInvoke il cui tipo restituito corrisponde al delegato. I parametri del metodo sono solo i parametri delegati esattamente che sono ByRef parametri, nello stesso ordine in cui si verificano nella firma del delegato. Oltre a questi parametri, è presente un parametro aggiuntivo di tipo System.IAsyncResult alla fine dell'elenco di parametri.

Esistono tre passaggi per definire e usare delegati: dichiarazione, creazione di istanze e chiamata.

I delegati vengono dichiarati usando la sintassi della dichiarazione del delegato. Nell'esempio seguente viene dichiarato un delegato denominato SimpleDelegate che non accetta argomenti:

Delegate Sub SimpleDelegate()

L'esempio seguente crea un'istanza SimpleDelegate e quindi la chiama immediatamente:

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

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

Non c'è molto punto per creare un'istanza di un delegato per un metodo e quindi chiamare immediatamente tramite il delegato, in quanto sarebbe più semplice chiamare direttamente il metodo. I delegati mostrano la loro utilità quando viene usato l'anonimato. Nell'esempio seguente viene illustrato un MultiCall metodo che chiama ripetutamente un'istanza SimpleDelegate di :

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

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

Non è importante per il MultiCall metodo qual è il metodo di destinazione per SimpleDelegate , qual è l'accessibilità di questo metodo o se il metodo è Shared o meno. Tutto ciò che conta è che la firma del metodo di destinazione è compatibile con SimpleDelegate.

Tipi parziali

Le dichiarazioni di classe e struttura possono essere dichiarazioni parziali . Una dichiarazione parziale può o non descrivere completamente il tipo dichiarato all'interno della dichiarazione. Al contrario, la dichiarazione del tipo può essere distribuita tra più dichiarazioni parziali all'interno del programma; I tipi parziali non possono essere dichiarati attraverso i limiti del programma. Una dichiarazione di tipo parziale specifica il Partial modificatore nella dichiarazione. Quindi, tutte le altre dichiarazioni nel programma per un tipo con lo stesso nome completo verranno unite insieme alla dichiarazione parziale in fase di compilazione per formare una singola dichiarazione di tipo. Ad esempio, il codice seguente dichiara una singola classe Test con membri Test.C1 e 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

Quando si combinano dichiarazioni di tipo parziale, almeno una delle dichiarazioni deve avere un modificatore. In caso contrario, viene restituito un Partial errore in fase di compilazione.

Nota. Sebbene sia possibile specificare Partial in una sola dichiarazione tra molte dichiarazioni parziali, è preferibile specificarla in tutte le dichiarazioni parziali. Nella situazione in cui una dichiarazione parziale è visibile, ma una o più dichiarazioni parziali sono nascoste (ad esempio, nel caso dell'estensione del codice generato dallo strumento), è accettabile lasciare il Partial modificatore fuori dalla dichiarazione visibile, ma specificarlo nelle dichiarazioni nascoste.

Solo le classi e le strutture possono essere dichiarate usando dichiarazioni parziali. L'arità di un tipo viene considerata quando si associano dichiarazioni parziali: due classi con lo stesso nome ma numeri diversi di parametri di tipo non vengono considerate dichiarazioni parziali dello stesso tempo. Le dichiarazioni parziali possono specificare attributi, modificatori di classi, Inherits istruzioni o Implements istruzioni. In fase di compilazione, tutte le parti delle dichiarazioni parziali vengono combinate e usate come parte della dichiarazione di tipo. Se sono presenti conflitti tra attributi, modificatori, basi, interfacce o membri di tipo, viene restituito un errore in fase di compilazione. Per esempio:

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

Nell'esempio precedente viene dichiarato un tipo Test1 , Publicche eredita da Object e implementa System.IDisposable e System.IComparable. Le dichiarazioni parziali di Test2 causeranno un errore in fase di compilazione perché una delle dichiarazioni indica che Test2 è Public e un altro indica che Test2 è Private.

I tipi parziali con parametri di tipo possono dichiarare vincoli e varianza per i parametri di tipo, ma i vincoli e la varianza di ogni dichiarazione parziale devono corrispondere. Pertanto, i vincoli e la varianza sono speciali in quanto non vengono combinati automaticamente come altri modificatori:

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

Il fatto che un tipo venga dichiarato utilizzando più dichiarazioni parziali non influisce sulle regole di ricerca del nome all'interno del tipo. Di conseguenza, una dichiarazione di tipo parziale può usare membri dichiarati in altre dichiarazioni di tipo parziale o può implementare metodi sulle interfacce dichiarate in altre dichiarazioni di tipo parziale. Per esempio:

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

Anche i tipi annidati possono avere dichiarazioni parziali. Per esempio:

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

Gli inizializzatori all'interno di una dichiarazione parziale verranno comunque eseguiti in ordine di dichiarazione; Tuttavia, non esiste un ordine di esecuzione garantito per gli inizializzatori che si verificano in dichiarazioni parziali separate.

Tipi costruiti

Una dichiarazione di tipo generico, da sola, non indica un tipo. È invece possibile usare una dichiarazione di tipo generico come "progetto" per formare molti tipi diversi applicando argomenti di tipo. A un tipo generico con argomenti di tipo applicati viene chiamato tipo costruito. Gli argomenti di tipo in un tipo costruito devono soddisfare sempre i vincoli inseriti nei parametri di tipo a cui corrispondono.

Un nome di tipo potrebbe identificare un tipo costruito anche se non specifica direttamente i parametri di tipo. Ciò può verificarsi quando un tipo è annidato all'interno di una dichiarazione di classe generica e il tipo di istanza della dichiarazione contenitore viene usato in modo implicito per la ricerca del nome:

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

Un tipo C(Of T1,...,Tn) costruito è accessibile quando il tipo generico e tutti gli argomenti di tipo sono accessibili. Ad esempio, se il tipo generico è Public e tutti gli argomenti T1,...,Tn di tipo sono Public, il tipo costruito è Public.C Se il nome del tipo o uno degli argomenti di tipo è Private, tuttavia, l'accessibilità del tipo costruito è Private. Se un argomento di tipo del tipo costruito è Protected e un altro argomento di tipo è Friend, il tipo costruito è accessibile solo nella classe e nelle relative sottoclassi in questo assembly o in qualsiasi assembly a cui è stato concesso Friend l'accesso. In altre parole, il dominio di accessibilità per un tipo costruito è l'intersezione dei domini di accessibilità delle parti costitutive.

Nota. Il fatto che il dominio di accessibilità del tipo costruito sia l'intersezione delle sue parti costituite ha l'effetto collaterale interessante di definire un nuovo livello di accessibilità. Tipo costruito che contiene un elemento che è Protected e un elemento accessibile Friend solo nei contesti che possono accedere a entrambiFriendi membri eProtected . Tuttavia, non esiste alcun modo per esprimere questo livello di accessibilità nel linguaggio, perché l'accessibilità Protected Friend significa che un'entità può essere accessibile in un contesto che può accedere a unoFriendoProtected ai membri.

Le interfacce di base, implementate e i membri dei tipi costruiti vengono determinati sostituendo gli argomenti di tipo forniti per ogni occorrenza del parametro di tipo nel tipo generico.

Tipi aperti e tipi chiusi

Un tipo costruito per chi uno o più argomenti di tipo sono parametri di tipo di un tipo o di un metodo contenitore viene chiamato tipo aperto. Ciò è dovuto al fatto che alcuni parametri di tipo del tipo non sono ancora noti, quindi la forma effettiva del tipo non è ancora completamente nota. Al contrario, un tipo generico i cui argomenti di tipo sono tutti parametri non di tipo è denominato tipo chiuso. La forma di un tipo chiuso è sempre nota. Per esempio:

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

Il tipo costruito è un tipo Base(Of Integer, V) aperto perché, sebbene sia stato fornito il parametro T di tipo, è stato fornito un altro parametro U di tipo. Pertanto, la forma completa del tipo non è ancora nota. Il tipo costruito , tuttavia, è un tipo Derived(Of Double)chiuso perché sono stati forniti tutti i parametri di tipo nella gerarchia di ereditarietà.

I tipi aperti sono definiti come segue:

  • Un parametro di tipo è un tipo aperto.

  • Un tipo di matrice è un tipo aperto se il tipo di elemento è un tipo aperto.

  • Un tipo costruito è un tipo aperto se uno o più degli argomenti di tipo sono un tipo aperto.

  • Un tipo chiuso è un tipo che non è un tipo aperto.

Poiché il punto di ingresso del programma non può essere in un tipo generico, tutti i tipi usati in fase di esecuzione saranno tipi chiusi.

Tipi speciali

.NET Framework contiene una serie di classi trattate appositamente da .NET Framework e dal linguaggio Visual Basic:

Il tipo System.Void, che rappresenta un tipo void in .NET Framework, può essere fatto riferimento direttamente solo nelle GetType espressioni.

I tipi System.RuntimeArgumentHandleSystem.ArgIterator e System.TypedReference tutti possono contenere puntatori nello stack e quindi non possono essere visualizzati nell'heap di .NET Framework. Di conseguenza, non possono essere usati come tipi di elemento matrice, tipi restituiti, tipi di campo, argomenti di tipo generico, tipi nullable, ByRef tipi di parametro, tipo di un valore convertito in Object o System.ValueType, destinazione di una chiamata ai membri dell'istanza di Object o System.ValueTypeo lifted in una chiusura.