Partager via


Types en Visual Basic

Les deux catégories fondamentales de types en Visual Basic sont des types valeur et des types de référence. Les types primitifs (à l’exception des chaînes), les énumérations et les structures sont des types valeur. Les classes, les chaînes, les modules standard, les interfaces, les tableaux et les délégués sont des types de référence.

Chaque type a une valeur par défaut, qui est la valeur affectée aux variables de ce type lors de l’initialisation.

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

Types valeur et types référence

Bien que les types valeur et les types de référence puissent être similaires en termes de syntaxe de déclaration et d’utilisation, leur sémantique est distincte.

Les types de référence sont stockés sur le tas d’exécution ; ils peuvent uniquement être accessibles par le biais d’une référence à ce stockage. Étant donné que les types de référence sont toujours accessibles via des références, leur durée de vie est gérée par le .NET Framework. Les références en suspens à une instance particulière sont suivies et l’instance est détruite uniquement quand aucune référence supplémentaire ne reste. Une variable de type référence contient une référence à une valeur de ce type, une valeur d’un type plus dérivé ou une valeur Null. Une valeur Null fait référence à rien ; il n’est pas possible de faire quoi que ce soit avec une valeur Null, sauf l’affecter. L’affectation à une variable d’un type référence crée une copie de la référence plutôt qu’une copie de la valeur référencée. Pour une variable d’un type référence, la valeur par défaut est une valeur Null.

Les types valeur sont stockés directement sur la pile, soit dans un tableau, soit dans un autre type ; leur stockage est accessible directement. Étant donné que les types valeur sont stockés directement dans des variables, leur durée de vie est déterminée par la durée de vie de la variable qui les contient. Lorsque l’emplacement contenant une instance de type valeur est détruit, l’instance de type valeur est également détruite. Les types valeur sont toujours accessibles directement ; il n’est pas possible de créer une référence à un type valeur. L’interdiction d’une telle référence rend impossible la référence à une instance de classe de valeur qui a été détruite. Étant donné que les types valeur sont toujours NotInheritable, une variable d’un type valeur contient toujours une valeur de ce type. En raison de cela, la valeur d’un type valeur ne peut pas être une valeur Null, ni référencer un objet d’un type plus dérivé. L’affectation à une variable d’un type valeur crée une copie de la valeur affectée. Pour une variable d’un type valeur, la valeur par défaut est le résultat de l’initialisation de chaque membre variable du type à sa valeur par défaut.

L’exemple suivant montre la différence entre les types de référence et les types valeur :

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 sortie du programme est la suivante :

Values: 0, 123
Refs: 123, 123

L’affectation à la variable val2 locale n’affecte pas la variable val1 locale, car les deux variables locales sont d’un type valeur (le type Integer) et chaque variable locale d’un type valeur a son propre stockage. En revanche, l’affectation ref2.Value = 123; affecte l’objet à la fois ref1 et ref2 référence.

Une chose à noter sur le système de type .NET Framework est que même si les structures, les énumérations et les types primitifs (à l’exception Stringde ) sont des types valeur, ils héritent tous des types de référence. Les structures et les types primitifs héritent du type System.ValueTypede référence, qui hérite de Object. Les types énumérés héritent du type System.Enumde référence, qui hérite de System.ValueType.

Types de valeurs nullables

Pour les types valeur, un ? modificateur peut être ajouté à un nom de type pour représenter la version nullable de ce type.

NullableTypeName
    : NonArrayTypeName '?'
    ;

NullableNameModifier
    : '?'
    ;

Un type valeur nullable peut contenir les mêmes valeurs que la version non nullable du type, ainsi que la valeur Null. Par conséquent, pour un type valeur nullable, l’affectation Nothing à une variable du type définit la valeur de la variable sur la valeur Null, et non la valeur zéro du type valeur. Par exemple:

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)

Une variable peut également être déclarée comme étant d’un type valeur nullable en plaçant un modificateur de type Nullable sur le nom de la variable. Pour plus de clarté, il n’est pas valide d’avoir un modificateur de type Nullable sur un nom de variable et un nom de type dans la même déclaration. Étant donné que les types nullables sont implémentés à l’aide du type System.Nullable(Of T), le type T? est synonyme du type System.Nullable(Of T)et les deux noms peuvent être utilisés de manière interchangeable. Le ? modificateur ne peut pas être placé sur un type qui est déjà nullable ; il n’est donc pas possible de déclarer le type Integer?? ou System.Nullable(Of Integer)?.

Un type T? valeur nullable a les membres de System.Nullable(Of T) ainsi que tous les opérateurs ou conversions levés du type T sous-jacent dans le type T?. La suppression des opérateurs et des conversions à partir du type sous-jacent, dans la plupart des cas, en remplaçant les types valeur nullables pour les types valeur non nullables. Cela permet à la plupart des mêmes conversions et opérations qui s’appliquent également à T s’appliquer T? .

Implémentation de l’interface

Les déclarations de structure et de classe peuvent déclarer qu’elles implémentent un ensemble de types d’interface via une ou plusieurs Implements clauses.

TypeImplementsClause
    : 'Implements' TypeImplements StatementTerminator
    ;

TypeImplements
    : NonArrayTypeName ( Comma NonArrayTypeName )*
    ;

Tous les types spécifiés dans la Implements clause doivent être des interfaces, et le type doit implémenter tous les membres des interfaces. Par exemple:

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

Type qui implémente une interface implémente également implicitement toutes les interfaces de base de l’interface. Cela est vrai même si le type ne répertorie pas explicitement toutes les interfaces de base dans la Implements clause. Dans cet exemple, la structure implémente à la TextBox fois IControl et 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

Déclarer qu’un type implémente une interface dans et de lui-même ne déclare rien dans l’espace de déclaration du type. Par conséquent, il est valide d’implémenter deux interfaces avec une méthode du même nom.

Les types ne peuvent pas implémenter un paramètre de type seul, même s’ils peuvent impliquer les paramètres de type qui se trouvent dans l’étendue.

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

    ...
End Class

Les interfaces génériques peuvent être implémentées plusieurs fois à l’aide d’arguments de type différents. Toutefois, un type générique ne peut pas implémenter une interface générique à l’aide d’un paramètre de type si le paramètre de type fourni (indépendamment des contraintes de type) peut chevaucher une autre implémentation de cette interface. Par exemple:

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

Types primitifs

Les types primitifs sont identifiés par le biais de mots clés, qui sont des alias pour les types prédéfinis dans l’espace System de noms. Un type primitif est complètement indistinguishable du type qu’il alias : l’écriture du mot Byte réservé est exactement la même que l’écriture System.Byte. Les types primitifs sont également appelés types intrinsèques.

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

NumericTypeName
    : IntegralTypeName
    | FloatingPointTypeName
    | 'Decimal'
    ;

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

FloatingPointTypeName
    : 'Single' | 'Double'
    ;

Étant donné qu’un type primitif alias un type normal, chaque type primitif a des membres. Par exemple, Integer les membres sont déclarés dans System.Int32. Les littéraux peuvent être traités comme des instances de leurs types correspondants.

  • Les types primitifs diffèrent des autres types de structure dans montrant qu’ils autorisent certaines opérations supplémentaires :

  • Les types primitifs permettent de créer des valeurs en écrivant des littéraux. Par exemple, 123I est un littéral de type Integer.

  • Il est possible de déclarer des constantes des types primitifs.

  • Lorsque les opérandes d’une expression sont toutes des constantes de type primitif, il est possible que le compilateur évalue l’expression au moment de la compilation. Une telle expression est appelée expression constante.

Visual Basic définit les types primitifs suivants :

  • Types de valeurs intégrales Byte (entier non signé de 1 octet), SByte (entier signé de 1 octet), (entier non signé de 2 octets), UShort (entier signé de 2 octets), ShortUInteger (entier signé de 4 octets), (entier signé de 4 octets), Integer (entier signé de 4 octets), (entier non signé de 8 octets) ULong et Long (entier signé de 8 octets). Ces types sont mappés à System.Byte, System.SByte, System.UInt16System.Int16, System.UInt32, System.Int32, System.UInt64 et System.Int64, respectivement. La valeur par défaut d’un type intégral est équivalente au littéral 0.

  • Types de valeurs Single à virgule flottante (virgule flottante de 4 octets) et Double (virgule flottante de 8 octets). Ces types sont mappés à System.Single et System.Double, respectivement. La valeur par défaut d’un type à virgule flottante équivaut au littéral 0.

  • Type Decimal (valeur décimale de 16 octets), qui correspond à System.Decimal. La valeur par défaut de la décimale équivaut au littéral 0D.

  • Type Boolean valeur, qui représente une valeur de vérité, généralement le résultat d’une opération relationnelle ou logique. Le littéral est de type System.Boolean. La valeur par défaut du Boolean type est équivalente au littéral False.

  • Type Date valeur, qui représente une date et/ou une heure et est mappé à System.DateTime. La valeur par défaut du Date type est équivalente au littéral # 01/01/0001 12:00:00AM #.

  • Type Char valeur, qui représente un caractère Unicode unique et mappe à System.Char. La valeur par défaut du Char type est équivalente à l’expression ChrW(0)constante.

  • Type String de référence, qui représente une séquence de caractères Unicode et mappé à System.String. La valeur par défaut du String type est une valeur Null.

Énumérations

Les énumérations sont des types valeur qui héritent et System.Enum représentent symboliquement un ensemble de valeurs d’un des types intégraux primitifs.

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

Pour un type Ed’énumération, la valeur par défaut est la valeur produite par l’expression CType(0, E).

Le type sous-jacent d’une énumération doit être un type intégral qui peut représenter toutes les valeurs d’énumérateur définies dans l’énumération. Si un type sous-jacent est spécifié, il doit être Byte, , ShortSByteUShort, UInteger, Integer, , ULong, Longou l’un de leurs types correspondants dans l’espace System de noms. Si aucun type sous-jacent n’est spécifié explicitement, la valeur par défaut est Integer.

L’exemple suivant déclare une énumération avec un type sous-jacent de Long:

Enum Color As Long
    Red
    Green
    Blue
End Enum

Un développeur peut choisir d’utiliser un type sous-jacent de Long, comme dans l’exemple, pour activer l’utilisation de valeurs qui se trouvent dans la plage Long, mais pas dans la plage de Integer, ou pour conserver cette option pour l’avenir.

Membres de l’énumération

Les membres d’une énumération sont les valeurs énumérées déclarées dans l’énumération et les membres hérités de la classe System.Enum.

L’étendue d’un membre d’énumération est le corps de déclaration d’énumération. Cela signifie qu’en dehors d’une déclaration d’énumération, un membre d’énumération doit toujours être qualifié (sauf si le type est spécifiquement importé dans un espace de noms via une importation d’espace de noms).

L’ordre de déclaration pour les déclarations de membre d’énumération est significatif lorsque les valeurs d’expression constante sont omises. Les membres d’énumération ont Public implicitement accès uniquement ; aucun modificateur d’accès n’est autorisé sur les déclarations de membre d’énumération.

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

Valeurs d’énumération

Les valeurs énumérées d’une liste de membres d’énumération sont déclarées en tant que constantes typées comme type d’énumération sous-jacent, et elles peuvent apparaître où que les constantes soient requises. Une définition de membre d’énumération avec donne = au membre associé la valeur indiquée par l’expression constante. L’expression constante doit évaluer un type intégral qui est implicitement convertible en type sous-jacent et doit se trouver dans la plage de valeurs qui peuvent être représentées par le type sous-jacent. L’exemple suivant est en erreur, car les valeurs 1.5constantes , 2.3et 3.3 ne sont pas implicitement convertibles en type Long intégral sous-jacent avec une sémantique stricte.

Option Strict On

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

Plusieurs membres d’énumération peuvent partager la même valeur associée, comme indiqué ci-dessous :

Enum Color
    Red
    Green
    Blue
    Max = Blue
End Enum

L’exemple montre une énumération qui a deux membres d’énumération -- Blue et Max -- qui ont la même valeur associée.

Si la première définition de valeur d’énumérateur dans l’énumération n’a pas d’initialiseur, la valeur de la constante correspondante est 0. Une définition de valeur d’énumération sans initialiseur donne à l’énumérateur la valeur obtenue en augmentant la valeur de la valeur d’énumération précédente par 1. Cette valeur accrue doit se trouver dans la plage de valeurs qui peuvent être représentées par le type sous-jacent.

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

L’exemple ci-dessus imprime les valeurs d’énumération et leurs valeurs associées. La sortie est la suivante :

Red = 0
Green = 10
Blue = 11

Les raisons des valeurs sont les suivantes :

  • La valeur Red d’énumération est automatiquement affectée à la valeur 0 (car elle n’a pas d’initialiseur et est le premier membre de la valeur d’énumération).

  • La valeur Green d’énumération est explicitement donnée à la valeur 10.

  • La valeur Blue d’énumération est automatiquement affectée à la valeur supérieure à la valeur d’énumération qui précède textuellement celle-ci.

L’expression constante ne peut pas utiliser directement ou indirectement la valeur de sa propre valeur d’énumération associée (autrement dit, la circularité dans l’expression constante n’est pas autorisée). L’exemple suivant n’est pas valide, car les déclarations et AB sont circulaires.

Enum Circular
    A = B
    B
End Enum

A B dépend explicitement, et B dépend A implicitement.

Cours

Une classe est une structure de données qui peut contenir des membres de données (constantes, variables et événements), des membres de fonction (méthodes, propriétés, indexeurs, opérateurs et constructeurs) et des types imbriqués. Les classes sont des types de référence.

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

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

L’exemple suivant montre une classe qui contient chaque type de membre :

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’exemple suivant montre les utilisations de ces membres :

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

Il existe deux modificateurs spécifiques à la classe, MustInherit et NotInheritable. Il n’est pas valide de les spécifier tous les deux.

Spécification de base de classe

Une déclaration de classe peut inclure une spécification de type de base qui définit le type de base direct de la classe.

ClassBase
    : 'Inherits' NonArrayTypeName StatementTerminator
    ;

Si une déclaration de classe n’a pas de type de base explicite, le type de base direct est implicitement Object. Par exemple:

Class Base
End Class

Class Derived
    Inherits Base
End Class

Les types de base ne peuvent pas être un paramètre de type seul, même s’ils peuvent impliquer les paramètres de type qui se trouvent dans l’étendue.

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

Les classes peuvent dériver uniquement des classes et des Object classes. Il n’est pas valide pour qu’une classe dérive de System.ValueType, System.Enum, System.ArraySystem.MulticastDelegate ou System.Delegate. Une classe générique ne peut pas dériver d’une System.Attribute classe qui en dérive.

Chaque classe a exactement une classe de base directe, et la circularité dans la dérivation est interdite. Il n’est pas possible de dériver d’une NotInheritable classe, et le domaine d’accessibilité de la classe de base doit être identique ou un sur-ensemble du domaine d’accessibilité de la classe elle-même.

Membres de classe

Les membres d’une classe se composent des membres introduits par ses déclarations de membre de classe et les membres hérités de sa classe de base directe.

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

Une déclaration de membre de classe peut avoir Public, , ProtectedFriend, Protected Friendou Private accéder. Lorsqu’une déclaration de membre de classe n’inclut pas de modificateur d’accès, la déclaration est par défaut Public d’accès, sauf s’il s’agit d’une déclaration de variable ; dans ce cas, elle accède par défaut Private .

L’étendue d’un membre de classe est le corps de classe dans lequel la déclaration de membre se produit, ainsi que la liste de contraintes de cette classe (si elle est générique et a des contraintes). Si le membre a Friend accès, son étendue s’étend au corps de classe d’une classe dérivée dans le même programme ou tout assembly auquel l’accès a été accordé Friend , et si le membre a Public, Protectedou Protected Friend a accès, son étendue s’étend au corps de classe d’une classe dérivée dans n’importe quel programme.

Structures

Les structures sont des types valeur qui héritent de System.ValueType. Les structures sont similaires aux classes dans lesquelles elles représentent des structures de données qui peuvent contenir des membres de données et des membres de fonction. Contrairement aux classes, toutefois, les structures ne nécessitent pas d’allocation de tas.

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

StructureModifier
    : TypeModifier
    | 'Partial'
    ;

Dans le cas des classes, il est possible que deux variables référencent le même objet, et ainsi que les opérations sur une variable affectent l’objet référencé par l’autre variable. Avec les structures, les variables ont chacune leur propre copie des données non-donnéesShared . Il n’est donc pas possible pour les opérations sur l’une d’elles d’affecter l’autre, comme l’illustre l’exemple suivant :

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

Étant donné la déclaration ci-dessus, le code suivant génère la valeur 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

Affectation de a créer b une copie de la valeur et b n’est donc pas affectée par l’affectation à a.x. Au Point lieu de cela, la sortie aurait 100 été déclarée en tant que classe, car a et b référencerait le même objet.

Membres de la structure

Les membres d’une structure sont les membres introduits par ses déclarations de membres de structure et les membres hérités de System.ValueType.

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

Chaque structure a implicitement un constructeur d’instance Public sans paramètre qui produit la valeur par défaut de la structure. Par conséquent, il n’est pas possible qu’une déclaration de type de structure déclare un constructeur d’instance sans paramètre. Toutefois, un type de structure est autorisé à déclarer des constructeurs d’instance paramétrables , comme dans l’exemple suivant :

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

Étant donné la déclaration ci-dessus, les instructions suivantes créent une Point instruction avec x et y initialisée à zéro.

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

Étant donné que les structures contiennent directement leurs valeurs de champ (plutôt que des références à ces valeurs), les structures ne peuvent pas contenir de champs qui se référencent directement ou indirectement. Par exemple, le code suivant n’est pas valide :

Structure S1
    Dim f1 As S2
End Structure

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

Normalement, une déclaration de membre de structure peut uniquement avoirPublic, Friendou Private accéder, mais en cas de substitution de membres hérités, ObjectProtected et Protected Friend l’accès peut également être utilisé. Lorsqu’une déclaration de membre de structure n’inclut pas de modificateur d’accès, la déclaration par défaut est Public accessible. L’étendue d’un membre déclaré par une structure est le corps de structure dans lequel la déclaration se produit, ainsi que les contraintes de cette structure (si elle était générique et avait des contraintes).

Standard Modules

Un module standard est un type dont les membres sont implicitement Shared et limités à l’espace de déclaration de l’espace de noms contenant le module standard, plutôt qu’à la déclaration de module standard elle-même. Les modules standard ne peuvent jamais être instanciés. Il s’agit d’une erreur pour déclarer une variable d’un type de module standard.

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

Un membre d’un module standard a deux noms qualifiés complets, un sans le nom du module standard et un avec le nom du module standard. Plusieurs modules standard dans un espace de noms peuvent définir un membre portant un nom particulier ; Les références non qualifiées au nom en dehors de l’un des deux modules sont ambiguës. Par exemple:

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 module peut uniquement être déclaré dans un espace de noms et ne peut pas être imbriqué dans un autre type. Les modules standard ne peuvent pas implémenter d’interfaces, ils dérivent implicitement de Object, et ils ont uniquement Shared des constructeurs.

Membres du module standard

Les membres d’un module standard sont les membres introduits par ses déclarations membres et les membres hérités de Object. Les modules standard peuvent avoir n’importe quel type de membre à l’exception des constructeurs d’instance. Tous les membres de type de module standard sont implicitement Shared.

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

Normalement, une déclaration de membre de module standard peut uniquement avoir Public, Friendou Private accéder, mais en cas de substitution de membres hérités, Objectles Protected modificateurs d’accès peuvent Protected Friend être spécifiés. Lorsqu’une déclaration de membre de module standard n’inclut pas de modificateur d’accès, la déclaration est par défaut Public d’accès, sauf s’il s’agit d’une variable, laquelle est par défaut accessible Private .

Comme indiqué précédemment, l’étendue d’un membre de module standard est la déclaration contenant la déclaration de module standard. Les membres hérités de Object ne sont pas inclus dans cette étendue spéciale ; ces membres n’ont pas d’étendue et doivent toujours être qualifiés avec le nom du module. Si le membre a Friend accès, son étendue s’étend uniquement aux membres de l’espace de noms déclarés dans le même programme ou les mêmes assemblys auxquels l’accès a été accordé Friend .

Interfaces

Les interfaces sont des types de référence que d’autres types implémentent pour garantir qu’ils prennent en charge certaines méthodes. Une interface n’est jamais créée directement et n’a aucune représentation réelle. D’autres types doivent être convertis en type d’interface. Une interface définit un contrat. Une classe ou une structure qui implémente une interface doit respecter son contrat.

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

L’exemple suivant montre une interface qui contient une propriété Itempar défaut, un événement E, une méthode Fet une propriété P:

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

    Event E()

    Sub F(value As Integer)

    Property P() As String
End Interface

Les interfaces peuvent utiliser plusieurs héritages. Dans l’exemple suivant, l’interface IComboBox hérite des deux ITextBox et 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

Les classes et les structures peuvent implémenter plusieurs interfaces. Dans l’exemple suivant, la classe EditBox dérive de la classe Control et implémente les deux IControl et 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

Héritage de l’interface

Les interfaces de base d’une interface sont les interfaces de base explicites et leurs interfaces de base. En d’autres termes, l’ensemble des interfaces de base est la fermeture transitive complète des interfaces de base explicites, leurs interfaces de base explicites, et ainsi de suite. Si une déclaration d’interface n’a pas de base d’interface explicite, il n’existe aucune interface de base pour le type - les interfaces n’héritent Object pas (bien qu’elles aient une conversion étendue en Object).

InterfaceBase
    : 'Inherits' InterfaceBases StatementTerminator
    ;

InterfaceBases
    : NonArrayTypeName ( Comma NonArrayTypeName )*
    ;

Dans l’exemple suivant, les interfaces de base sont IComboBoxIControl, ITextBoxet 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

Une interface hérite de tous les membres de ses interfaces de base. En d’autres termes, l’interface IComboBox ci-dessus hérite des membres SetText et SetItems ainsi que Paint.

Une classe ou une structure qui implémente une interface implémente implicitement toutes les interfaces de base de l’interface.

Si une interface apparaît plusieurs fois dans la fermeture transitive des interfaces de base, elle contribue uniquement à ses membres à l’interface dérivée une seule fois. Un type implémentant l’interface dérivée doit uniquement implémenter les méthodes de l’interface de base définie une seule fois. Dans l’exemple suivant, Paint il ne doit être implémenté qu’une seule fois, même si la classe implémente IComboBox et 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

Une Inherits clause n’a aucun effet sur d’autres Inherits clauses. Dans l’exemple suivant, IDerived doit qualifier le nom de IBaseINested .

Interface IBase
    Interface INested
        Sub Nested()
    End Interface

    Sub Base()
End Interface

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

Le domaine d’accessibilité d’une interface de base doit être identique ou un sur-ensemble du domaine d’accessibilité de l’interface elle-même.

Membres de l’interface

Les membres d’une interface se composent des membres introduits par ses déclarations de membre et les membres hérités de ses interfaces de base.

InterfaceMemberDeclaration
    : NonModuleDeclaration
    | InterfaceEventMemberDeclaration
    | InterfaceMethodMemberDeclaration
    | InterfacePropertyMemberDeclaration
    ;

Bien que les interfaces n’héritent pas des Objectmembres, car chaque classe ou structure qui implémente une interface hérite Object, les membres des méthodes d’extensionObject, sont considérés comme membres d’une interface et peuvent être appelés directement sur une interface sans nécessiter de cast .Object Par exemple:

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

Membres d’une interface portant le même nom que les membres de membres implicitement d’ombre ObjectObject . Seuls les types, méthodes, propriétés et événements imbriqués peuvent être membres d’une interface. Les méthodes et les propriétés peuvent ne pas avoir de corps. Les membres de l’interface sont implicitement Public et peuvent ne pas spécifier de modificateur d’accès. L’étendue d’un membre déclaré dans une interface est le corps de l’interface dans lequel la déclaration se produit, ainsi que la liste de contraintes de cette interface (si elle est générique et a des contraintes).

Tableaux

Un tableau est un type de référence qui contient des variables accessibles via des index correspondant d’une manière un-à-un avec l’ordre des variables dans le tableau. Les variables contenues dans un tableau, également appelées éléments du tableau, doivent toutes être du même type, et ce type est appelé type d’élément du tableau.

ArrayTypeName
    : NonArrayTypeName ArrayTypeModifiers
    ;

ArrayTypeModifiers
    : ArrayTypeModifier+
    ;

ArrayTypeModifier
    : OpenParenthesis RankList? CloseParenthesis
    ;

RankList
    : Comma*
    ;

ArrayNameModifier
    : ArrayTypeModifiers
    | ArraySizeInitializationModifier
    ;

Les éléments d’un tableau entrent en existence lorsqu’une instance de tableau est créée et cessent d’exister lorsque l’instance de tableau est détruite. Chaque élément d’un tableau est initialisé avec la valeur par défaut de son type. Le type System.Array est le type de base de tous les types de tableaux et peut ne pas être instancié. Chaque type de tableau hérite des membres déclarés par le System.Array type et est convertible en lui (et Object). Un type de tableau unidimensionnel avec un élément T implémente également les interfaces System.Collections.Generic.IList(Of T) et IReadOnlyList(Of T); s’il T s’agit d’un type référence, le type de tableau implémente IList(Of U) également et IReadOnlyList(Of U) pour tous U ceux qui ont une conversion de référence étendue à partir de T.

Un tableau a un rang qui détermine le nombre d’index associés à chaque élément de tableau. Le rang d’un tableau détermine le nombre de dimensions du tableau. Par exemple, un tableau avec un rang d’un est appelé tableau unidimensionnel, et un tableau dont le rang est supérieur à celui d’un tableau multidimensionnel.

L’exemple suivant crée un tableau unidimensionnel de valeurs entières, initialise les éléments de tableau, puis les imprime :

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

Le programme génère les résultats suivants :

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

Chaque dimension d’un tableau a une longueur associée. Les longueurs de dimension ne font pas partie du type du tableau, mais sont établies lorsqu’une instance du type de tableau est créée au moment de l’exécution. La longueur d’une dimension détermine la plage valide d’index pour cette dimension : pour une dimension de longueur N, les index peuvent passer de zéro à N-1. Si une dimension est de longueur zéro, il n’y a pas d’index valides pour cette dimension. Le nombre total d’éléments d’un tableau est le produit des longueurs de chaque dimension du tableau. Si l’une des dimensions d’un tableau a une longueur de zéro, le tableau est dit vide. Le type d’élément d’un tableau peut être n’importe quel type.

Les types de tableaux sont spécifiés en ajoutant un modificateur à un nom de type existant. Le modificateur se compose d’une parenthèse gauche, d’un ensemble de zéro ou plus de virgules et d’une parenthèse droite. Le type modifié est le type d’élément du tableau, et le nombre de dimensions est le nombre de virgules plus une. Si plusieurs modificateurs sont spécifiés, le type d’élément du tableau est un tableau. Les modificateurs sont lus de gauche à droite, le modificateur le plus à gauche étant le tableau le plus externe. Dans l'exemple

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

le type d’élément de arr est un tableau à deux dimensions de tableaux tridimensionnels de tableaux unidimensionnels de Integer.

Une variable peut également être déclarée comme étant d’un type de tableau en plaçant un modificateur de type de tableau ou un modificateur d’initialisation de taille de tableau sur le nom de la variable. Dans ce cas, le type d’élément de tableau est le type donné dans la déclaration, et les dimensions du tableau sont déterminées par le modificateur de nom de variable. Pour plus de clarté, il n’est pas valide d’avoir un modificateur de type de tableau sur un nom de variable et un nom de type dans la même déclaration.

L’exemple suivant montre une variété de déclarations de variables locales qui utilisent des types de tableaux avec Integer comme type d’élément :

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 modificateur de nom de type de tableau s’étend à tous les ensembles de parenthèses qui le suivent. Cela signifie que dans les situations où un ensemble d’arguments placés entre parenthèses est autorisé après un nom de type, il n’est pas possible de spécifier les arguments d’un nom de type de tableau. Par exemple:

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

Dans le dernier cas, (3) est interprété comme faisant partie du nom de type plutôt que comme un ensemble d’arguments de constructeur.

Délégués

Un délégué est un type référence qui fait référence à une Shared méthode d’un type ou à une méthode d’instance d’un objet.

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

MethodSignature
    : SubSignature
    | FunctionSignature
    ;

L’équivalent le plus proche d’un délégué dans d’autres langages est un pointeur de fonction, mais alors qu’un pointeur de fonction ne peut référencer Shared que des fonctions, un délégué peut référencer à la fois Shared et les méthodes d’instance. Dans ce dernier cas, le délégué stocke non seulement une référence au point d’entrée de la méthode, mais également une référence à l’instance d’objet avec laquelle appeler la méthode.

La déclaration de délégué peut ne pas avoir de Handles clause, une Implements clause, un corps de méthode ou une End construction. La liste des paramètres de la déclaration de délégué peut ne pas contenir ou ParamArray paramètresOptional. Le domaine d’accessibilité du type de retour et des types de paramètres doit être identique ou un sur-ensemble du domaine d’accessibilité du délégué lui-même.

Les membres d’un délégué sont les membres hérités de la classe System.Delegate. Un délégué définit également les méthodes suivantes :

  • Constructeur qui accepte deux paramètres, un de type Object et un de type System.IntPtr.

  • Méthode Invoke qui a la même signature que le délégué.

  • Méthode BeginInvoke dont la signature est la signature de délégué, avec trois différences. Tout d’abord, le type de retour est remplacé par System.IAsyncResult. Deuxièmement, deux paramètres supplémentaires sont ajoutés à la fin de la liste des paramètres : le premier de type System.AsyncCallback et le second de type Object. Enfin, tous les ByRef paramètres sont modifiés pour être ByVal.

  • Méthode EndInvoke dont le type de retour est identique au délégué. Les paramètres de la méthode sont uniquement les paramètres délégués exactement qui sont ByRef des paramètres, dans le même ordre qu’ils se produisent dans la signature de délégué. En plus de ces paramètres, il existe un paramètre de type System.IAsyncResult supplémentaire à la fin de la liste des paramètres.

Il existe trois étapes pour définir et utiliser des délégués : la déclaration, l’instanciation et l’appel.

Les délégués sont déclarés à l’aide de la syntaxe de déclaration de délégué. L’exemple suivant déclare un délégué nommé SimpleDelegate qui ne prend aucun argument :

Delegate Sub SimpleDelegate()

L’exemple suivant crée une SimpleDelegate instance, puis l’appelle immédiatement :

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

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

Il n’y a pas beaucoup de points dans l’instanciation d’un délégué pour une méthode, puis l’appel immédiat via le délégué, car il serait plus simple d’appeler la méthode directement. Les délégués montrent leur utilité lorsque leur anonymat est utilisé. L’exemple suivant montre une MultiCall méthode qui appelle à plusieurs reprises une SimpleDelegate instance :

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

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

Il n’est pas important pour la MultiCall méthode que la méthode cible pour l’objet SimpleDelegate est, quelle accessibilité cette méthode a, ou si la méthode est Shared ou non. Tout cela importe, c’est que la signature de la méthode cible est compatible avec SimpleDelegate.

Types partiels

Les déclarations de classe et de structure peuvent être des déclarations partielles . Une déclaration partielle peut ou non décrire complètement le type déclaré dans la déclaration. Au lieu de cela, la déclaration du type peut être répartie sur plusieurs déclarations partielles au sein du programme ; les types partiels ne peuvent pas être déclarés entre les limites du programme. Une déclaration de type partielle spécifie le Partial modificateur sur la déclaration. Ensuite, toutes les autres déclarations du programme pour un type portant le même nom complet seront fusionnées avec la déclaration partielle au moment de la compilation pour former une déclaration de type unique. Par exemple, le code suivant déclare une classe Test unique avec des membres Test.C1 et 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

Lors de la combinaison de déclarations de type partiel, au moins l’une des déclarations doit avoir un Partial modificateur, sinon une erreur au moment de la compilation se produit.

Remarque. Bien qu’il soit possible de spécifier Partial sur une seule déclaration parmi de nombreuses déclarations partielles, il est préférable de le spécifier sur toutes les déclarations partielles. Dans le cas où une déclaration partielle est visible, mais qu’une ou plusieurs déclarations partielles sont masquées (par exemple, le cas d’extension du code généré par l’outil), il est acceptable de laisser le Partial modificateur hors de la déclaration visible, mais de le spécifier sur les déclarations masquées.

Seules les classes et les structures peuvent être déclarées à l’aide de déclarations partielles. L’arité d’un type est prise en compte lors de la mise en correspondance de déclarations partielles ensemble : deux classes portant le même nom, mais différents nombres de paramètres de type ne sont pas considérés comme des déclarations partielles en même temps. Les déclarations partielles peuvent spécifier des attributs, des modificateurs de classe, Inherits une instruction ou Implements une instruction. Au moment de la compilation, toutes les parties des déclarations partielles sont combinées et utilisées dans le cadre de la déclaration de type. S’il existe des conflits entre les attributs, les modificateurs, les bases, les interfaces ou les membres de type, une erreur au moment de la compilation se produit. Par exemple:

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

L’exemple précédent déclare un type Test1 qui Publicest , hérite et Object implémente System.IDisposable et System.IComparable. Les déclarations partielles d’une erreur au moment de Test2 la compilation, car l’une des déclarations indique que Test2 c’est et un autre dit que c’est Test2PublicPrivate.

Les types partiels avec des paramètres de type peuvent déclarer des contraintes et une variance pour les paramètres de type, mais les contraintes et la variance de chaque déclaration partielle doivent correspondre. Ainsi, les contraintes et la variance sont spéciales dans le fait qu’elles ne sont pas combinées automatiquement comme d’autres modificateurs :

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

Le fait qu’un type est déclaré à l’aide de plusieurs déclarations partielles n’affecte pas les règles de recherche de noms dans le type. Par conséquent, une déclaration de type partiel peut utiliser des membres déclarés dans d’autres déclarations de type partielles ou implémenter des méthodes sur les interfaces déclarées dans d’autres déclarations de type partiel. Par exemple:

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

Les types imbriqués peuvent également avoir des déclarations partielles. Par exemple:

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

Les initialiseurs dans une déclaration partielle seront toujours exécutés dans l’ordre de déclaration ; toutefois, il n’existe aucun ordre d’exécution garanti pour les initialiseurs qui se produisent dans des déclarations partielles distinctes.

Types construits

Une déclaration de type générique, par lui-même, ne désigne pas un type. Au lieu de cela, une déclaration de type générique peut être utilisée comme « blueprint » pour former de nombreux types différents en appliquant des arguments de type. Un type générique qui a des arguments de type appliqués à celui-ci est appelé type construit. Les arguments de type d’un type construit doivent toujours satisfaire les contraintes placées sur les paramètres de type auxquels ils correspondent.

Un nom de type peut identifier un type construit, même s’il ne spécifie pas directement les paramètres de type. Cela peut se produire lorsqu’un type est imbriqué dans une déclaration de classe générique et que le type d’instance de la déclaration conteneur est implicitement utilisé pour la recherche de noms :

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 type C(Of T1,...,Tn) construit est accessible lorsque le type générique et tous les arguments de type sont accessibles. Par exemple, si le type C générique est Public et que tous les arguments T1,...,Tn de type sont Public, le type construit est Public. Si le nom de type ou l’un des arguments de type est Private, toutefois, l’accessibilité du type construit est Private. Si un argument de type du type construit est Protected et qu’un autre argument de type est Friend, le type construit est accessible uniquement dans la classe et ses sous-classes dans cet assembly ou tout assembly ayant reçu Friend l’accès. En d’autres termes, le domaine d’accessibilité d’un type construit est l’intersection des domaines d’accessibilité de ses parties constituantes.

Remarque. Le fait que le domaine d’accessibilité du type construit est l’intersection de ses parties constituées a l’effet secondaire intéressant de définir un nouveau niveau d’accessibilité. Type construit qui contient un élément qui est Protected et un élément accessible Friend uniquement dans des contextes qui peuvent accéder aux deuxFriendetProtected aux membres. Toutefois, il n’existe aucun moyen d’exprimer ce niveau d’accessibilité dans la langue, car l’accessibilité Protected Friend signifie qu’une entité est accessible dans un contexte pouvant accéder à l’un ou l’autreFrienddesProtected membres.

Les interfaces de base, implémentées et membres de types construits sont déterminées en remplaçant les arguments de type fournis pour chaque occurrence du paramètre de type dans le type générique.

Types ouverts et types fermés

Un type construit pour qui un ou plusieurs arguments de type sont des paramètres de type d’un type ou d’une méthode contenant est appelé type ouvert. Cela est dû au fait que certains paramètres de type du type ne sont toujours pas connus, de sorte que la forme réelle du type n’est pas encore entièrement connue. En revanche, un type générique dont les arguments de type sont tous les paramètres non de type est appelé type fermé. La forme d’un type fermé est toujours entièrement connue. Par exemple:

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

Le type Base(Of Integer, V) construit est un type ouvert, car bien que le paramètre T de type ait été fourni, le paramètre U de type a été fourni un autre paramètre de type. Ainsi, la forme complète du type n’est pas encore connue. Toutefois, le type Derived(Of Double)construit est un type fermé, car tous les paramètres de type de la hiérarchie d’héritage ont été fournis.

Les types ouverts sont définis comme suit :

  • Un paramètre de type est un type ouvert.

  • Un type de tableau est un type ouvert si son type d’élément est un type ouvert.

  • Un type construit est un type ouvert si un ou plusieurs de ses arguments de type sont un type ouvert.

  • Un type fermé est un type qui n’est pas un type ouvert.

Étant donné que le point d’entrée du programme ne peut pas se trouver dans un type générique, tous les types utilisés au moment de l’exécution seront fermés.

Types spéciaux

Le .NET Framework contient un certain nombre de classes traitées spécialement par le .NET Framework et par le langage Visual Basic :

Le type System.Void, qui représente un type void dans le .NET Framework, peut être référencé directement uniquement dans les GetType expressions.

Les types System.RuntimeArgumentHandle, System.ArgIterator et System.TypedReference tous peuvent contenir des pointeurs dans la pile et ne peuvent donc pas apparaître sur le tas .NET Framework. Par conséquent, ils ne peuvent pas être utilisés comme types d’éléments de tableau, types de retour, types de champs, arguments de type générique, types nullables, ByRef types de paramètres, type d’une valeur en Object cours de conversion en ou System.ValueType, cible d’un appel en membres d’instance de Object ou System.ValueType, ou levée dans une fermeture.