Membri del tipo

I membri dei tipi definiscono percorsi di archiviazione e codice eseguibile. Possono essere metodi, costruttori, eventi, costanti, variabili e proprietà.

Implementazione del metodo di interfaccia

Metodi, eventi e proprietà possono implementare membri dell'interfaccia. Per implementare un membro di interfaccia, una dichiarazione membro specifica la Implements parola chiave e elenca uno o più membri dell'interfaccia.

ImplementsClause
    : ( 'Implements' ImplementsList )?
    ;

ImplementsList
    : InterfaceMemberSpecifier ( Comma InterfaceMemberSpecifier )*
    ;

InterfaceMemberSpecifier
    : NonArrayTypeName Period IdentifierOrKeyword
    ;

I metodi e le proprietà che implementano i membri dell'interfaccia sono implicitamente NotOverridable a meno che non siano dichiarati come MustOverride, Overridableo che eseguano l'override di un altro membro. Si tratta di un errore per un membro che implementa un membro di interfaccia come Shared. L'accessibilità di un membro non ha alcun effetto sulla possibilità di implementare i membri dell'interfaccia.

Affinché un'implementazione dell'interfaccia sia valida, l'elenco implementa del tipo contenitore deve assegnare un nome a un'interfaccia contenente un membro compatibile. Un membro compatibile è un membro la cui firma corrisponde alla firma del membro di implementazione. Se viene implementata un'interfaccia generica, l'argomento di tipo fornito nella clausola Implements viene sostituito nella firma durante il controllo della compatibilità. Per esempio:

Interface I1(Of T)
    Sub F(x As T)
End Interface

Class C1
    Implements I1(Of Integer)

    Sub F(x As Integer) Implements I1(Of Integer).F
    End Sub
End Class

Class C2(Of U)
    Implements I1(Of U)

    Sub F(x As U) Implements I1(Of U).F
    End Sub
End Class

Se un evento dichiarato usando un tipo delegato implementa un evento di interfaccia, un evento compatibile è uno il cui tipo delegato sottostante è lo stesso tipo. In caso contrario, l'evento usa il tipo delegato dell'evento di interfaccia che sta implementando. Se un evento di questo tipo implementa più eventi di interfaccia, tutti gli eventi dell'interfaccia devono avere lo stesso tipo di delegato sottostante. Per esempio:

Interface ClickEvents
    Event LeftClick(x As Integer, y As Integer)
    Event RightClick(x As Integer, y As Integer)
End Interface

Class Button
    Implements ClickEvents

    ' OK. Signatures match, delegate type = ClickEvents.LeftClickHandler.
    Event LeftClick(x As Integer, y As Integer) _
        Implements ClickEvents.LeftClick

    ' OK. Signatures match, delegate type = ClickEvents.RightClickHandler.
    Event RightClick(x As Integer, y As Integer) _
        Implements ClickEvents.RightClick
End Class

Class Label
    Implements ClickEvents

    ' Error. Signatures match, but can't be both delegate types.
    Event Click(x As Integer, y As Integer) _
        Implements ClickEvents.LeftClick, ClickEvents.RightClick
End Class

Un membro dell'interfaccia nell'elenco implements viene specificato usando un nome di tipo, un punto e un identificatore. Il nome del tipo deve essere un'interfaccia nell'elenco implements o un'interfaccia di base di un'interfaccia nell'elenco implements e l'identificatore deve essere un membro dell'interfaccia specificata. Un singolo membro può implementare più membri di interfaccia corrispondenti.

Interface ILeft
    Sub F()
End Interface

Interface IRight
    Sub F()
End Interface

Class Test
    Implements ILeft, IRight

    Sub F() Implements ILeft.F, IRight.F
    End Sub
End Class

Se il membro di interfaccia implementato non è disponibile in tutte le interfacce implementate in modo esplicito a causa dell'ereditarietà di più interfacce, il membro di implementazione deve fare riferimento in modo esplicito a un'interfaccia di base su cui è disponibile il membro. Ad esempio, se I1 e I2 contengono un membro Me I3 eredita da I1 e I2, un tipo implementato I3 implementerà I1.M e I2.M. Se un'ombreggiatura dell'interfaccia moltiplica i membri ereditati, un tipo di implementazione dovrà implementare i membri ereditati e i membri che li ombreranno.

Interface ILeft
    Sub F()
End Interface

Interface IRight
    Sub F()
End Interface

Interface ILeftRight
    Inherits ILeft, IRight

    Shadows Sub F()
End Interface

Class Test
    Implements ILeftRight

    Sub LeftF() Implements ILeft.F
    End Sub

    Sub RightF() Implements IRight.F
    End Sub

    Sub LeftRightF() Implements ILeftRight.F
    End Sub
End Class

Se l'interfaccia contenitore del membro di interfaccia deve essere implementata è generica, è necessario specificare gli stessi argomenti di tipo dell'interfaccia implementata. Per esempio:

Interface I1(Of T)
    Function F() As T
End Interface

Class C1
    Implements I1(Of Integer)
    Implements I1(Of Double)

    Function F1() As Integer Implements I1(Of Integer).F
    End Function

    Function F2() As Double Implements I1(Of Double).F
    End Function

    ' Error: I1(Of String) is not implemented by C1
    Function F3() As String Implements I1(Of String).F
    End Function
End Class

Class C2(Of U)
    Implements I1(Of U)

    Function F() As U Implements I1(Of U).F
    End Function
End Class

Metodi

I metodi contengono le istruzioni eseguibili di un programma.

MethodMemberDeclaration
    : MethodDeclaration
    | ExternalMethodDeclaration
    ;

InterfaceMethodMemberDeclaration
    : InterfaceMethodDeclaration
    ;

MethodDeclaration
    : SubDeclaration
    | MustOverrideSubDeclaration
    | FunctionDeclaration
    | MustOverrideFunctionDeclaration
    ;

InterfaceMethodDeclaration
    : InterfaceSubDeclaration
    | InterfaceFunctionDeclaration
    ;

SubSignature
    : 'Sub' Identifier TypeParameterList?
      ( OpenParenthesis ParameterList? CloseParenthesis )?
    ;

FunctionSignature
    : 'Function' Identifier TypeParameterList?
      ( OpenParenthesis ParameterList? CloseParenthesis )?
      ( 'As' Attributes? TypeName )?
    ;

SubDeclaration
    : Attributes? ProcedureModifier* SubSignature
      HandlesOrImplements? LineTerminator
      Block
      'End' 'Sub' StatementTerminator
    ;

MustOverrideSubDeclaration
    : Attributes? MustOverrideProcedureModifier+ SubSignature
      HandlesOrImplements? StatementTerminator
    ;

InterfaceSubDeclaration
    : Attributes? InterfaceProcedureModifier* SubSignature StatementTerminator
    ;

FunctionDeclaration
    : Attributes? ProcedureModifier* FunctionSignature
      HandlesOrImplements? LineTerminator
      Block
      'End' 'Function' StatementTerminator
    ;

MustOverrideFunctionDeclaration
    : Attributes? MustOverrideProcedureModifier+ FunctionSignature
      HandlesOrImplements? StatementTerminator
    ;

InterfaceFunctionDeclaration
    : Attributes? InterfaceProcedureModifier* FunctionSignature StatementTerminator
    ;

ProcedureModifier
    : AccessModifier | 'Shadows' | 'Shared' | 'Overridable' | 'NotOverridable' | 'Overrides'
    | 'Overloads' | 'Partial' | 'Iterator' | 'Async'
    ;

MustOverrideProcedureModifier
    : ProcedureModifier
    | 'MustOverride'
    ;

InterfaceProcedureModifier
    : 'Shadows' | 'Overloads'
    ;

HandlesOrImplements
    : HandlesClause
    | ImplementsClause
    ;

I metodi, con un elenco facoltativo di parametri e un valore restituito facoltativo, sono condivisi o non condivisi. È possibile accedere ai metodi condivisi tramite la classe o le istanze della classe . I metodi non condivisi, detti anche metodi di istanza, sono accessibili tramite istanze della classe . Nell'esempio seguente viene illustrata una classe Stack con diversi metodi condivisi (Clone e Flip) e diversi metodi di istanza (Push, Pope ToString):

Public Class Stack
    Public Shared Function Clone(s As Stack) As Stack
        ...
    End Function

    Public Shared Function Flip(s As Stack) As Stack
        ...
    End Function

    Public Function Pop() As Object
        ...
    End Function

    Public Sub Push(o As Object)
        ...
    End Sub 

    Public Overrides Function ToString() As String
        ...
    End Function 
End Class 

Module Test
    Sub Main()
        Dim s As Stack = New Stack()
        Dim i As Integer

        While i < 10
            s.Push(i)
        End While

        Dim flipped As Stack = Stack.Flip(s)
        Dim cloned As Stack = Stack.Clone(s)

        Console.WriteLine("Original stack: " & s.ToString())
        Console.WriteLine("Flipped stack: " & flipped.ToString())
        Console.WriteLine("Cloned stack: " & cloned.ToString())
    End Sub
End Module

È possibile eseguire l'overload dei metodi, il che significa che più metodi possono avere lo stesso nome purché abbiano firme univoche. La firma di un metodo è costituita dal numero e dai tipi dei relativi parametri. La firma di un metodo in particolare non include i modificatori di parametri o di tipo restituito, ad esempio Facoltativo, ByRef o ParamArray. L'esempio seguente illustra una classe con un numero di overload:

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

    Sub F(o As Object)
        Console.WriteLine("F(Object)")
    End Sub

    Sub F(value As Integer)
        Console.WriteLine("F(Integer)")
    End Sub 

    Sub F(a As Integer, b As Integer)
        Console.WriteLine("F(Integer, Integer)")
    End Sub 

    Sub F(values() As Integer)
        Console.WriteLine("F(Integer())")
    End Sub 

    Sub G(s As String, Optional s2 As String = 5)
        Console.WriteLine("G(String, Optional String")
    End Sub

    Sub G(s As String)
        Console.WriteLine("G(String)")
    End Sub


    Sub Main()
        F()
        F(1)
        F(CType(1, Object))
        F(1, 2)
        F(New Integer() { 1, 2, 3 })
        G("hello")
        G("hello", "world")
    End Sub
End Module

L'output del programma è:

F()
F(Integer)
F(Object)
F(Integer, Integer)
F(Integer())
G(String)
G(String, Optional String)

Gli overload che differiscono solo nei parametri facoltativi possono essere usati per il "controllo delle versioni" delle librerie. Ad esempio, v1 di una libreria può includere una funzione con parametri facoltativi:

Sub fopen(fileName As String, Optional accessMode as Integer = 0)

Quindi v2 della libreria vuole aggiungere un altro parametro facoltativo "password" e vuole farlo senza interrompere la compatibilità dell'origine (in modo che le applicazioni usate per la destinazione v1 possano essere ricompilate) e senza interrompere la compatibilità binaria (in modo che le applicazioni usate per fare riferimento alla versione 1 possano ora fare riferimento alla versione 2 senza ricompilare). Ecco come verrà visualizzata la versione 2:

Sub fopen(file As String, mode as Integer)
Sub fopen(file As String, Optional mode as Integer = 0, Optional pword As String = "")

Si noti che i parametri facoltativi in un'API pubblica non sono conformi a CLS. Tuttavia, possono essere utilizzate almeno da Visual Basic e C#4 e F#.

Dichiarazioni di metodi Regular, Async e Iterator

Esistono due tipi di metodi: subroutine, che non restituiscono valori e funzioni, che eseguono. Il corpo e End il costrutto di un metodo possono essere omessi solo se il metodo è definito in un'interfaccia o ha il MustOverride modificatore. Se non viene specificato alcun tipo restituito in una funzione e viene usata una semantica rigorosa, si verifica un errore in fase di compilazione; in caso contrario, il tipo è implicito Object o il tipo del carattere di tipo del metodo. Il dominio di accessibilità del tipo restituito e dei tipi di parametro di un metodo deve essere uguale o un superset del dominio di accessibilità del metodo stesso.

Un metodo regolare è uno con né Async modificatori né Iterator . Può trattarsi di una subroutine o di una funzione. Sezione Metodi regolari illustra in dettaglio cosa accade quando viene richiamato un metodo regolare.

Un metodo iteratore è uno con il Iterator modificatore e nessun Async modificatore. Deve essere una funzione e il tipo restituito deve essere IEnumerator, IEnumerableo o IEnumerator(Of T)IEnumerable(Of T) per alcuni Te non ByRef deve avere parametri. I metodi iteratore sezione illustrano in dettaglio cosa accade quando viene richiamato un metodo iteratore.

Un metodo asincrono è uno con il Async modificatore e nessun Iterator modificatore. Deve essere una subroutine o una funzione con tipo Task restituito o Task(Of T) per alcuni Te non ByRef deve avere parametri. La sezione Metodi asincroni illustra in dettaglio cosa accade quando viene richiamato un metodo asincrono.

Si tratta di un errore in fase di compilazione se un metodo non è uno di questi tre tipi di metodo.

Le dichiarazioni subroutine e di funzione sono speciali in quanto le istruzioni iniziale e finale devono iniziare ogni riga all'inizio di una riga logica. Inoltre, il corpo di unaMustOverride dichiarazione di funzione o subroutine non deve iniziare all'inizio di una riga logica. Per esempio:

Module Test
    ' Illegal: Subroutine doesn't start the line
    Public x As Integer : Sub F() : End Sub

    ' Illegal: First statement doesn't start the line
    Sub G() : Console.WriteLine("G")
    End Sub

    ' Illegal: End Sub doesn't start the line
    Sub H() : End Sub
End Module

Dichiarazioni di metodi esterni

Una dichiarazione di metodo esterno introduce un nuovo metodo la cui implementazione viene fornita esternamente al programma.

ExternalMethodDeclaration
    : ExternalSubDeclaration
    | ExternalFunctionDeclaration
    ;

ExternalSubDeclaration
    : Attributes? ExternalMethodModifier* 'Declare' CharsetModifier? 'Sub'
      Identifier LibraryClause AliasClause?
      ( OpenParenthesis ParameterList? CloseParenthesis )? StatementTerminator
    ;

ExternalFunctionDeclaration
    : Attributes? ExternalMethodModifier* 'Declare' CharsetModifier? 'Function'
      Identifier LibraryClause AliasClause?
      ( OpenParenthesis ParameterList? CloseParenthesis )?
      ( 'As' Attributes? TypeName )?
      StatementTerminator
    ;

ExternalMethodModifier
    : AccessModifier
    | 'Shadows'
    | 'Overloads'
    ;

CharsetModifier
    : 'Ansi' | 'Unicode' | 'Auto'
    ;

LibraryClause
    : 'Lib' StringLiteral
    ;

AliasClause
    : 'Alias' StringLiteral
    ;

Poiché una dichiarazione di metodo esterno non fornisce alcuna implementazione effettiva, non ha alcun corpo o End costrutto del metodo. I metodi esterni vengono condivisi in modo implicito, potrebbero non avere parametri di tipo e potrebbero non gestire eventi o implementare membri dell'interfaccia. Se non viene specificato alcun tipo restituito in una funzione e viene utilizzata una semantica strict, si verifica un errore in fase di compilazione. In caso contrario, il tipo è implicito Object o il tipo del carattere di tipo del metodo. Il dominio di accessibilità del tipo restituito e dei tipi di parametro di un metodo esterno deve essere uguale o un superset del dominio di accessibilità del metodo esterno stesso.

La clausola library di una dichiarazione di metodo esterno specifica il nome del file esterno che implementa il metodo . La clausola alias facoltativa è una stringa che specifica l'ordinale numerico (preceduto da un # carattere) o il nome del metodo nel file esterno. È anche possibile specificare un modificatore di set a caratteri singolo, che regola il set di caratteri usato per effettuare il marshalling delle stringhe durante una chiamata al metodo esterno. Il Unicode modificatore effettua il marshalling di tutte le stringhe in valori Unicode, il modificatore effettua il Ansi marshalling di tutte le stringhe in valori ANSI e il modificatore effettua il Auto marshalling delle stringhe in base alle regole di .NET Framework in base al nome del metodo o al nome dell'alias, se specificato. Se non viene specificato alcun modificatore, il valore predefinito è Ansi.

Se Ansi o Unicode è specificato, il nome del metodo viene cercato nel file esterno senza alcuna modifica. Se Auto viene specificato, la ricerca del nome del metodo dipende dalla piattaforma. Se la piattaforma è considerata ANSI (ad esempio, Windows 95, Windows 98, Windows ME), il nome del metodo viene cercato senza alcuna modifica. Se la ricerca ha esito negativo, viene accodato un oggetto A e la ricerca ha provato di nuovo. Se la piattaforma è considerata Unicode (ad esempio, Windows NT, Windows 2000, Windows XP), viene aggiunto un W e il nome viene cercato. Se la ricerca ha esito negativo, la ricerca viene riprovata senza W. Per esempio:

Module Test
    ' All platforms bind to "ExternSub".
    Declare Ansi Sub ExternSub Lib "ExternDLL" ()

    ' All platforms bind to "ExternSub".
    Declare Unicode Sub ExternSub Lib "ExternDLL" ()

    ' ANSI platforms: bind to "ExternSub" then "ExternSubA".
    ' Unicode platforms: bind to "ExternSubW" then "ExternSub".
    Declare Auto Sub ExternSub Lib "ExternDLL" ()
End Module

I tipi di dati passati ai metodi esterni vengono sottoposti a marshalling in base alle convenzioni di marshalling dei dati di .NET Framework con un'unica eccezione. Le variabili stringa passate per valore ( ovvero ByVal x As String) vengono sottoposto a marshalling al tipo BSTR di automazione OLE e le modifiche apportate al BSTR nel metodo esterno vengono riflesse nell'argomento stringa. Questo perché il tipo String nei metodi esterni è modificabile e questo marshalling speciale simula tale comportamento. I parametri stringa passati per riferimento (ad esempio ByRef x As String) vengono sottoposto a marshalling come puntatore al tipo BSTR di automazione OLE. È possibile eseguire l'override di questi comportamenti speciali specificando l'attributo System.Runtime.InteropServices.MarshalAsAttribute nel parametro .

L'esempio illustra l'uso di metodi esterni:

Class Path
    Declare Function CreateDirectory Lib "kernel32" ( _
        Name As String, sa As SecurityAttributes) As Boolean
    Declare Function RemoveDirectory Lib "kernel32" ( _
        Name As String) As Boolean
    Declare Function GetCurrentDirectory Lib "kernel32" ( _
        BufSize As Integer, Buf As String) As Integer
    Declare Function SetCurrentDirectory Lib "kernel32" ( _
        Name As String) As Boolean
End Class

Metodi sostituibili

Il Overridable modificatore indica che un metodo è sostituibile. Il Overrides modificatore indica che un metodo esegue l'override di un metodo sottoposto a override di tipo base con la stessa firma. Il NotOverridable modificatore indica che un metodo sostituibile non può essere sottoposto a override. Il MustOverride modificatore indica che un metodo deve essere sottoposto a override nelle classi derivate.

Alcune combinazioni di questi modificatori non sono valide:

  • Overridable e NotOverridable si escludono a vicenda e non possono essere combinati.

  • MustOverride implica Overridable (e quindi non può specificarlo) e non può essere combinato con NotOverridable.

  • NotOverridable non può essere combinato con Overridable o MustOverride e deve essere combinato con Overrides.

  • Overrides implica Overridable (e quindi non può specificarlo) e non può essere combinato con MustOverride.

Esistono anche restrizioni aggiuntive sui metodi sostituibili:

  • Un MustOverride metodo potrebbe non includere un corpo del metodo o un End costrutto, potrebbe non eseguire l'override di un altro metodo e può essere visualizzato solo nelle MustInherit classi.

  • Se un metodo specifica Overrides e non esiste alcun metodo di base corrispondente da eseguire per l'override, si verifica un errore in fase di compilazione. Un metodo di override potrebbe non specificare Shadows.

  • Un metodo potrebbe non eseguire l'override di un altro metodo se il dominio di accessibilità del metodo di override non è uguale al dominio di accessibilità del metodo sottoposto a override. Un'eccezione è che un metodo che esegue l'override di un Protected Friend metodo in un altro assembly che non dispone Friend dell'accesso deve specificare Protected (non Protected Friend).

  • Private i metodi non possono essere Overridable, NotOverridableo MustOverride, né possono eseguire l'override di altri metodi.

  • I metodi nelle NotInheritable classi non possono essere dichiarati Overridable o MustOverride.

L'esempio seguente illustra le differenze tra metodi sostituibili e non sovrascrivibili:

Class Base
    Public Sub F()
        Console.WriteLine("Base.F")
    End Sub

    Public Overridable Sub G()
        Console.WriteLine("Base.G")
    End Sub
End Class

Class Derived
    Inherits Base

    Public Shadows Sub F()
        Console.WriteLine("Derived.F")
    End Sub

    Public Overrides Sub G()
        Console.WriteLine("Derived.G")
    End Sub
End Class

Module Test
    Sub Main()
        Dim d As Derived = New Derived()
        Dim b As Base = d

        b.F()
        d.F()
        b.G()
        d.G()
    End Sub
End Module

Nell'esempio la classe Base introduce un metodo F e un Overridable metodo G. La classe Derived introduce un nuovo metodo F, shadowing dell'oggetto ereditato Fed esegue anche l'override del metodo Gereditato . L'esempio produce l'output seguente:

Base.F
Derived.F
Derived.G
Derived.G

Si noti che l'istruzione b.G() richiama Derived.G, non Base.G. Ciò è dovuto al fatto che il tipo di runtime dell'istanza (ovvero Derived) anziché il tipo in fase di compilazione dell'istanza (ovvero Base) determina l'implementazione effettiva del metodo da richiamare.

Metodi condivisi

Il Shared modificatore indica che un metodo è un metodo condiviso. Un metodo condiviso non opera su un'istanza specifica di un tipo e può essere richiamato direttamente da un tipo anziché tramite una particolare istanza di un tipo. È tuttavia valido usare un'istanza per qualificare un metodo condiviso. Non è valido fare riferimento a Me, MyClasso MyBase in un metodo condiviso. I metodi condivisi potrebbero non essere Overridable, NotOverridableo MustOverridee potrebbero non eseguire l'override dei metodi. I metodi definiti nei moduli e nelle interfacce standard potrebbero non specificare Shared, perché sono già impliciti Shared .

Un metodo dichiarato in una struttura o in una classe senza un Shared modificatore è un metodo di istanza. Un metodo di istanza opera su una determinata istanza di un tipo. I metodi di istanza possono essere richiamati solo tramite un'istanza di un tipo e possono fare riferimento all'istanza tramite l'espressione Me .

L'esempio seguente illustra le regole per l'accesso ai membri condivisi e dell'istanza:

Class Test
    Private x As Integer
    Private Shared y As Integer

    Sub F()
        x = 1 ' Ok, same as Me.x = 1.
        y = 1 ' Ok, same as Test.y = 1.
    End Sub

    Shared Sub G()
        x = 1 ' Error, cannot access Me.x.
        y = 1 ' Ok, same as Test.y = 1.
    End Sub

    Shared Sub Main()
        Dim t As Test = New Test()

        t.x = 1 ' Ok.
        t.y = 1 ' Ok.
        Test.x = 1 ' Error, cannot access instance member through type.
        Test.y = 1 ' Ok.
    End Sub
End Class

Il metodo F mostra che in un membro della funzione dell'istanza, è possibile usare un identificatore per accedere sia ai membri dell'istanza che ai membri condivisi. Il metodo G mostra che in un membro della funzione condivisa si tratta di un errore per accedere a un membro dell'istanza tramite un identificatore. Il metodo Main mostra che in un'espressione di accesso ai membri è necessario accedere ai membri dell'istanza tramite istanze, ma è possibile accedere ai membri condivisi tramite tipi o istanze.

Parametri dei metodi

Un parametro è una variabile che può essere usata per passare informazioni da e verso un metodo. I parametri di un metodo vengono dichiarati dall'elenco di parametri del metodo, costituito da uno o più parametri separati da virgole.

ParameterList
    : Parameter ( Comma Parameter )*
    ;

Parameter
    : Attributes? ParameterModifier* ParameterIdentifier ( 'As' TypeName )?
      ( Equals ConstantExpression )?
    ;

ParameterModifier
    : 'ByVal' | 'ByRef' | 'Optional' | 'ParamArray'
    ;

ParameterIdentifier
    : Identifier IdentifierModifiers
    ;

Se non viene specificato alcun tipo per un parametro e viene utilizzata una semantica strict, si verifica un errore in fase di compilazione. In caso contrario, il tipo predefinito è Object o il tipo del carattere di tipo del parametro. Anche in semantica permissiva, se un parametro include una As clausola, tutti i parametri devono specificare tipi.

I parametri vengono specificati come parametri value, reference, optional o paramarray rispettivamente dai modificatori ByVal, ByRef, Optionale ParamArray. Parametro che non specifica ByRef o ByVal per impostazione predefinita .ByVal

I nomi dei parametri hanno come ambito l'intero corpo del metodo e sono sempre accessibili pubblicamente. Una chiamata al metodo crea una copia, specifica per tale chiamata, dei parametri e l'elenco di argomenti della chiamata assegna valori o riferimenti a variabili ai parametri appena creati. Poiché le dichiarazioni di metodi esterni e le dichiarazioni delegate non hanno corpo, i nomi dei parametri duplicati sono consentiti negli elenchi di parametri, ma sconsigliati.

L'identificatore può essere seguito dal modificatore ? del nome nullable per indicare che è nullable e anche dai modificatori del nome della matrice per indicare che si tratta di una matrice. Possono essere combinati, ad esempio "ByVal x?() As Integer". Non è consentito usare limiti di matrice espliciti; inoltre, se il modificatore del nome nullable è presente, deve essere presente una As clausola .

Parametri valore

Un parametro value viene dichiarato con un modificatore esplicito ByVal . Se viene utilizzato il ByVal modificatore, il ByRef modificatore potrebbe non essere specificato. Un parametro value è presente con la chiamata del membro a cui appartiene il parametro e viene inizializzato con il valore dell'argomento specificato nella chiamata. Un parametro value cessa di esistere al momento della restituzione del membro.

È consentito assegnare nuovi valori a un parametro di valore. Tali assegnazioni influiscono solo sulla posizione di archiviazione locale rappresentata dal parametro value; non hanno alcun effetto sull'argomento effettivo specificato nella chiamata al metodo.

Un parametro value viene usato quando il valore di un argomento viene passato in un metodo e le modifiche del parametro non influiscono sull'argomento originale. Un parametro value fa riferimento alla propria variabile, una distinta dalla variabile dell'argomento corrispondente. Questa variabile viene inizializzata copiando il valore dell'argomento corrispondente. Nell'esempio seguente viene illustrato un metodo F con un parametro di valore denominato p:

Module Test
    Sub F(p As Integer)
        Console.WriteLine("p = " & p)
        p += 1
    End Sub 

    Sub Main()
        Dim a As Integer = 1

        Console.WriteLine("pre: a = " & a)
        F(a)
        Console.WriteLine("post: a = " & a)
    End Sub
End Module

L'esempio produce l'output seguente, anche se il parametro p value viene modificato:

pre: a = 1
p = 1
post: a = 1

Parametri di riferimento

Un parametro di riferimento è un parametro dichiarato con un ByRef modificatore. Se viene specificato il ByRef modificatore, il ByVal modificatore potrebbe non essere utilizzato. Un parametro di riferimento non crea una nuova posizione di archiviazione. Un parametro di riferimento rappresenta invece la variabile specificata come argomento nella chiamata al metodo o al costruttore. Concettualmente, il valore di un parametro di riferimento è sempre uguale alla variabile sottostante.

I parametri di riferimento agiscono in due modalità, come alias o tramite copy-in copy-back.

Alias. Un parametro di riferimento viene usato quando il parametro funge da alias per un argomento fornito dal chiamante. Un parametro di riferimento non definisce una variabile, ma fa piuttosto riferimento alla variabile dell'argomento corrispondente. Le modifiche di un parametro di riferimento direttamente e influisce immediatamente sull'argomento corrispondente. L'esempio seguente illustra un metodo Swap con due parametri di riferimento:

Module Test
    Sub Swap(ByRef a As Integer, ByRef b As Integer)
        Dim t As Integer = a
        a = b
        b = t
    End Sub 

    Sub Main()
        Dim x As Integer = 1
        Dim y As Integer = 2

        Console.WriteLine("pre: x = " & x & ", y = " & y)
        Swap(x, y)
        Console.WriteLine("post: x = " & x & ", y = " & y)
    End Sub 
End Module

L'output del programma è:

pre: x = 1, y = 2
post: x = 2, y = 1

Per la chiamata del metodo Swap nella classe Main, a rappresenta x, e b rappresenta y. Pertanto, la chiamata ha l'effetto di scambiare i valori di x e y.

In un metodo che accetta parametri di riferimento, è possibile che più nomi rappresentino la stessa posizione di archiviazione:

Module Test
    Private s As String

    Sub F(ByRef a As String, ByRef b As String)
        s = "One"
        a = "Two"
        b = "Three"
    End Sub

    Sub G()
        F(s, s)
    End Sub
End Module

Nell'esempio la chiamata del metodo F in passa un riferimento a s per e ab.G Pertanto, per tale chiamata, i nomi s, ae b tutti fanno riferimento alla stessa posizione di archiviazione e le tre assegnazioni modificano tutte la variabile sdi istanza .

Copy-in copyback. Se il tipo della variabile passata a un parametro di riferimento non è compatibile con il tipo del parametro di riferimento o se una variabile non (ad esempio una proprietà) viene passata come argomento a un parametro di riferimento o se la chiamata è ad associazione tardiva, viene allocata una variabile temporanea e passata al parametro di riferimento. Il valore passato in verrà copiato in questa variabile temporanea prima che il metodo venga richiamato e verrà copiato nuovamente nella variabile originale (se presente e se è scrivibile) quando il metodo restituisce. Pertanto, un parametro di riferimento potrebbe non contenere necessariamente un riferimento all'archiviazione esatta della variabile passata e le eventuali modifiche apportate al parametro di riferimento potrebbero non essere riflesse nella variabile fino all'uscita dal metodo. Per esempio:

Class Base
End Class

Class Derived
    Inherits Base
End Class

Module Test
    Sub F(ByRef b As Base)
        b = New Base()
    End Sub

    Property G() As Base
        Get
        End Get
        Set
        End Set
    End Property

    Sub Main()
        Dim d As Derived

        F(G)   ' OK.
        F(d)   ' Throws System.InvalidCastException after F returns.
    End Sub
End Module

Nel caso della prima chiamata di F, viene creata una variabile temporanea e il valore della proprietà G viene assegnato e passato a F. Al momento della restituzione da F, il valore nella variabile temporanea viene assegnato di nuovo alla proprietà di G. Nel secondo caso viene creata un'altra variabile temporanea e il valore di d viene assegnato e passato a F. Quando si restituisce da F, il valore nella variabile temporanea viene restituito al tipo della variabile , Derivede assegnato a d. Poiché il valore passato di nuovo non può essere sottoposto a cast a Derived, viene generata un'eccezione in fase di esecuzione.

Parametri facoltativi

Un parametro facoltativo viene dichiarato con il Optional modificatore. I parametri che seguono un parametro facoltativo nell'elenco dei parametri formali devono anche essere facoltativi; l'errore di specificare il Optional modificatore nei parametri seguenti attiverà un errore in fase di compilazione. Un parametro facoltativo di un tipo T? nullable o un tipo T non nullable deve specificare un'espressione e costante da utilizzare come valore predefinito se non viene specificato alcun argomento. Se e restituisce Nothing di tipo Object, il valore predefinito del tipo di parametro verrà usato come valore predefinito per il parametro . In caso contrario, CType(e, T) deve essere un'espressione costante e viene considerata come predefinita per il parametro .

I parametri facoltativi sono l'unica situazione in cui un inizializzatore su un parametro è valido. L'inizializzazione viene sempre eseguita come parte dell'espressione di chiamata, non all'interno del corpo del metodo stesso.

Module Test
    Sub F(x As Integer, Optional y As Integer = 20)
        Console.WriteLine("x = " & x & ", y = " & y)
    End Sub

    Sub Main()
        F(10)
        F(30,40)
    End Sub
End Module

L'output del programma è:

x = 10, y = 20
x = 30, y = 40

I parametri facoltativi non possono essere specificati nelle dichiarazioni di delegato o evento, né nelle espressioni lambda.

Parametri ParamArray

ParamArray i parametri vengono dichiarati con il ParamArray modificatore. Se il ParamArray modificatore è presente, il ByVal modificatore deve essere specificato e nessun altro parametro può usare il ParamArray modificatore. Il ParamArray tipo del parametro deve essere una matrice unidimensionale e deve essere l'ultimo parametro nell'elenco dei parametri.

Un ParamArray parametro rappresenta un numero indeterminato di parametri del tipo di ParamArray. All'interno del metodo stesso, un ParamArray parametro viene considerato come tipo dichiarato e non ha una semantica speciale. Un ParamArray parametro è facoltativo in modo implicito, con un valore predefinito di una matrice unidimensionale vuota del tipo di ParamArray.

Un ParamArray consente di specificare gli argomenti in uno dei due modi in una chiamata al metodo:

  • L'argomento specificato per un ParamArray può essere una singola espressione di un tipo che si estende al ParamArray tipo . In questo caso, l'oggetto ParamArray agisce esattamente come un parametro di valore.

  • In alternativa, la chiamata può specificare zero o più argomenti per , ParamArraydove ogni argomento è un'espressione di un tipo convertibile in modo implicito nel tipo di elemento dell'oggetto ParamArray. In questo caso, la chiamata crea un'istanza del ParamArray tipo con una lunghezza corrispondente al numero di argomenti, inizializza gli elementi dell'istanza della matrice con i valori di argomento specificati e usa l'istanza della matrice appena creata come argomento effettivo.

Ad eccezione di un numero variabile di argomenti in una chiamata, un ParamArray è esattamente equivalente a un parametro value dello stesso tipo, come illustrato nell'esempio seguente.

Module Test
    Sub F(ParamArray args() As Integer)
        Dim i As Integer

        Console.Write("Array contains " & args.Length & " elements:")
        For Each i In args
            Console.Write(" " & i)
        Next i
        Console.WriteLine()
    End Sub

    Sub Main()
        Dim a As Integer() = { 1, 2, 3 }

        F(a)
        F(10, 20, 30, 40)
        F()
    End Sub
End Module

L'esempio produce l'output

Array contains 3 elements: 1 2 3
Array contains 4 elements: 10 20 30 40
Array contains 0 elements:

La prima chiamata di F passa semplicemente la matrice a come parametro di valore. La seconda chiamata di F crea automaticamente una matrice a quattro elementi con i valori di elemento specificati e passa tale istanza di matrice come parametro di valore. Analogamente, la terza chiamata di F crea una matrice di elementi zero e passa tale istanza come parametro di valore. La seconda e la terza chiamata sono esattamente equivalenti alla scrittura:

F(New Integer() {10, 20, 30, 40})
F(New Integer() {})

ParamArray I parametri non possono essere specificati nelle dichiarazioni di delegato o evento.

Gestione di eventi

I metodi possono gestire in modo dichiarativo gli eventi generati da oggetti in variabili condivise o di istanza. Per gestire gli eventi, una dichiarazione di metodo specifica la Handles parola chiave ed elenca uno o più eventi.

HandlesClause
    : ( 'Handles' EventHandlesList )?
    ;

EventHandlesList
    : EventMemberSpecifier ( Comma EventMemberSpecifier )*
    ;

EventMemberSpecifier
    : Identifier Period IdentifierOrKeyword
    | 'MyBase' Period IdentifierOrKeyword
    | 'MyClass' Period IdentifierOrKeyword
    | 'Me' Period IdentifierOrKeyword
    ;

Un evento nell'elenco Handles viene specificato da due identificatori separati da un punto:

  • Il primo identificatore deve essere un'istanza o una variabile condivisa nel tipo contenitore che specifica il WithEvents modificatore o la MyBase parola chiave o MyClassMe . In caso contrario, si verifica un errore in fase di compilazione. Questa variabile contiene l'oggetto che genererà gli eventi gestiti da questo metodo.

  • Il secondo identificatore deve specificare un membro del tipo del primo identificatore. Il membro deve essere un evento e può essere condiviso. Se viene specificata una variabile condivisa per il primo identificatore, l'evento deve essere condiviso o viene restituito un errore.

Un metodo M del gestore è considerato un gestore eventi valido per un evento E se l'istruzione AddHandler E, AddressOf M sarebbe valida. A differenza di un'istruzione AddHandler , tuttavia, i gestori eventi espliciti consentono di gestire un evento con un metodo senza argomenti indipendentemente dal fatto che venga usata o meno la semantica rigorosa:

Option Strict On

Class C1
    Event E(x As Integer)
End Class

Class C2
    withEvents C1 As New C1()

    ' Valid
    Sub M1() Handles C1.E
    End Sub

    Sub M2()
        ' Invalid
        AddHandler C1.E, AddressOf M1
    End Sub
End Class

Un singolo membro può gestire più eventi corrispondenti e più metodi possono gestire un singolo evento. L'accessibilità di un metodo non ha alcun effetto sulla possibilità di gestire gli eventi. L'esempio seguente illustra come un metodo può gestire gli eventi:

Class Raiser
    Event E1()

    Sub Raise()
        RaiseEvent E1
    End Sub
End Class

Module Test
    WithEvents x As Raiser

    Sub E1Handler() Handles x.E1
        Console.WriteLine("Raised")
    End Sub

    Sub Main()
        x = New Raiser()
        x.Raise()
        x.Raise()
    End Sub
End Module

Verrà visualizzato il codice seguente:

Raised
Raised

Un tipo eredita tutti i gestori eventi forniti dal tipo di base. Un tipo derivato non può modificare in alcun modo i mapping eventi che eredita dai relativi tipi di base, ma può aggiungere altri gestori all'evento.

Metodi di estensione

I metodi possono essere aggiunti ai tipi dall'esterno della dichiarazione di tipo usando metodi di estensione. I metodi di estensione sono metodi con l'attributo System.Runtime.CompilerServices.ExtensionAttribute applicato. Possono essere dichiarati solo nei moduli standard e devono avere almeno un parametro, che specifica il tipo esteso dal metodo. Ad esempio, il metodo di estensione seguente estende il tipo String:

Imports System.Runtime.CompilerServices

Module StringExtensions
    <Extension> _
    Sub Print(s As String)
        Console.WriteLine(s)
    End Sub
End Module

Nota. Sebbene Visual Basic richieda metodi di estensione da dichiarare in un modulo standard, altri linguaggi come C# possono consentire la dichiarazione in altri tipi di tipi. Se i metodi seguono le altre convenzioni descritte qui e il tipo contenitore non è un tipo generico aperto e non può essere creata un'istanza, Visual Basic riconoscerà i metodi di estensione.

Quando viene richiamato un metodo di estensione, l'istanza su cui viene richiamato viene passata al primo parametro. Il primo parametro non può essere dichiarato Optional o ParamArray. Qualsiasi tipo, incluso un parametro di tipo, può essere visualizzato come primo parametro di un metodo di estensione. Ad esempio, i metodi seguenti estendono i tipi Integer(), qualsiasi tipo che implementa System.Collections.Generic.IEnumerable(Of T)e qualsiasi tipo:

Imports System.Runtime.CompilerServices

Module Extensions
    <Extension> _
    Sub PrintArray(a() As Integer)
        ...
    End Sub

    <Extension> _
    Sub PrintList(Of T)(a As IEnumerable(Of T))
        ...
    End Sub

    <Extension> _
    Sub Print(Of T)(a As T)
        ...
    End Sub
End Module

Come illustrato nell'esempio precedente, le interfacce possono essere estese. I metodi di estensione dell'interfaccia forniscono l'implementazione del metodo, quindi i tipi che implementano un'interfaccia con metodi di estensione definiti in esso implementano ancora solo i membri originariamente dichiarati dall'interfaccia. Per esempio:

Imports System.Runtime.CompilerServices

Interface IAction
  Sub DoAction()
End Interface

Module IActionExtensions 
    <Extension> _
    Public Sub DoAnotherAction(i As IAction) 
        i.DoAction()
    End Sub
End Module

Class C
  Implements IAction

  Sub DoAction() Implements IAction.DoAction
    ...
  End Sub

  ' ERROR: Cannot implement extension method IAction.DoAnotherAction
  Sub DoAnotherAction() Implements IAction.DoAnotherAction
    ...
  End Sub
End Class

I metodi di estensione possono anche avere vincoli di tipo sui parametri di tipo e, analogamente ai metodi generici non di estensione, è possibile dedurre l'argomento di tipo:

Imports System.Runtime.CompilerServices

Module IEnumerableComparableExtensions
    <Extension> _
    Public Function Sort(Of T As IComparable(Of T))(i As IEnumerable(Of T)) _
        As IEnumerable(Of T)
        ...
    End Function
End Module

È anche possibile accedere ai metodi di estensione tramite espressioni di istanza implicite all'interno del tipo esteso:

Imports System.Runtime.CompilerServices

Class C1
    Sub M1()
        Me.M2()
        M2()
    End Sub
End Class

Module C1Extensions
    <Extension>
    Sub M2(c As C1)
        ...
    End Sub
End Module

Ai fini dell'accessibilità, i metodi di estensione vengono considerati anche come membri del modulo standard in cui sono dichiarati. Non hanno accesso aggiuntivo ai membri del tipo che stanno estendendo oltre l'accesso di cui dispongono in virtù del contesto di dichiarazione.

I metodi di estensione sono disponibili solo quando il metodo del modulo standard è nell'ambito. In caso contrario, il tipo originale non sarà stato esteso. Per esempio:

Imports System.Runtime.CompilerServices

Class C1
End Class

Namespace N1
    Module C1Extensions
        <Extension> _
        Sub M1(c As C1)
            ...
        End Sub
    End Module
End Namespace

Module Test
    Sub Main()
        Dim c As New C1()

        ' Error: c has no member named "M1"
        c.M1()
    End Sub
End Module

Facendo riferimento a un tipo quando è disponibile solo un metodo di estensione sul tipo, verrà comunque generato un errore in fase di compilazione.

È importante notare che i metodi di estensione vengono considerati membri del tipo in tutti i contesti in cui sono associati membri, ad esempio il modello fortemente tipizzato For Each . Per esempio:

Imports System.Runtime.CompilerServices

Class C1
End Class

Class C1Enumerator
    ReadOnly Property Current() As C1
        Get
            ...
        End Get
    End Property

    Function MoveNext() As Boolean
        ...
    End Function
End Class

Module C1Extensions
    <Extension> _
    Function GetEnumerator(c As C1) As C1Enumerator
        ...
    End Function
End Module

Module Test
    Sub Main()
        Dim c As New C1()

        ' Valid
        For Each o As Object In c
            ...
        Next o
    End Sub
End Module

È anche possibile creare delegati che fanno riferimento ai metodi di estensione. Di conseguenza, il codice:

Delegate Sub D1()

Module Test
    Sub Main()
        Dim s As String = "Hello, World!"
        Dim d As D1

        d = AddressOf s.Print
        d()
    End Sub
End Module

è approssimativamente equivalente a:

Delegate Sub D1()

Module Test
    Sub Main()
      Dim s As String = "Hello, World!"
      Dim d As D1

      d = CType([Delegate].CreateDelegate(GetType(D1), s, _
                GetType(StringExtensions).GetMethod("Print")), D1)
      d()
    End Sub
End Module

Nota. Visual Basic inserisce in genere un controllo su una chiamata al metodo di istanza che causa l'esecuzione di un System.NullReferenceException oggetto se l'istanza su cui viene richiamato il metodo è Nothing. Nel caso dei metodi di estensione, non esiste un modo efficiente per inserire questo controllo, quindi i metodi di estensione dovranno controllare in modo esplicito la presenza Nothingdi .

Nota. Un tipo valore verrà sottoposto a boxing quando viene passato come ByVal argomento a un parametro tipizzato come interfaccia. Ciò implica che gli effetti collaterali del metodo di estensione funzioneranno su una copia della struttura anziché sull'originale. Anche se il linguaggio non applica restrizioni al primo argomento di un metodo di estensione, è consigliabile che i metodi di estensione non vengano usati per estendere i tipi valore o che quando si estendono i tipi valore, il primo parametro viene passato ByRef per garantire che gli effetti collaterali funzionino sul valore originale.

Metodi parziali

Un metodo parziale è un metodo che specifica una firma ma non il corpo del metodo. Il corpo del metodo può essere fornito da un'altra dichiarazione del metodo con lo stesso nome e firma, molto probabilmente in un'altra dichiarazione parziale del tipo. Per esempio:

a.vb:

' Designer generated code
Public Partial Class MyForm
    Private Partial Sub ValidateControls()
    End Sub

    Public Sub New()
        ' Initialize controls
        ...

        ValidateControls()
    End Sub    
End Class

b.vb:

Public Partial Class MyForm
    Public Sub ValidateControls()
        ' Validation logic goes here
        ...
    End Sub
End Class

In questo esempio una dichiarazione parziale della classe MyForm dichiara un metodo ValidateControls parziale senza implementazione. Il costruttore nella dichiarazione parziale chiama il metodo parziale, anche se nel file non è specificato alcun corpo. L'altra dichiarazione parziale di MyForm fornisce quindi l'implementazione del metodo .

I metodi parziali possono essere chiamati indipendentemente dal fatto che sia stato fornito un corpo; se non viene fornito alcun corpo del metodo, la chiamata viene ignorata. Per esempio:

Public Class C1
    Private Partial Sub M1()
    End Sub

    Public Sub New()
        ' Since no implementation is supplied, this call will not be made.
        M1()
    End Sub
End Class

Tutte le espressioni passate come argomenti a una chiamata di metodo parziale ignorate vengono ignorate e non valutate. (Nota. Ciò significa che i metodi parziali sono un modo molto efficiente per fornire un comportamento definito in due tipi parziali, poiché i metodi parziali non hanno alcun costo se non vengono usati.

La dichiarazione di metodo parziale deve essere dichiarata come Private e deve essere sempre una subroutine senza istruzioni nel corpo. I metodi parziali non possono implementare metodi di interfaccia, anche se il metodo che fornisce il corpo può.

Un solo metodo può fornire un corpo a un metodo parziale. Un metodo che fornisce un corpo a un metodo parziale deve avere la stessa firma del metodo parziale, gli stessi vincoli per tutti i parametri di tipo, gli stessi modificatori di dichiarazione e gli stessi nomi di parametro di parametro e tipo. Gli attributi per il metodo parziale e il metodo che fornisce il relativo corpo vengono uniti, come tutti gli attributi nei parametri dei metodi. Analogamente, l'elenco di eventi gestiti dai metodi viene unito. Per esempio:

Class C1
    Event E1()
    Event E2()

    Private Partial Sub S() Handles Me.E1
    End Sub

    ' Handles both E1 and E2
    Private Sub S() Handles Me.E2
        ...
    End Sub
End Class

Costruttori

I costruttori sono metodi speciali che consentono il controllo sull'inizializzazione. Vengono eseguiti dopo l'inizio del programma o quando viene creata un'istanza di un tipo. A differenza di altri membri, i costruttori non vengono ereditati e non introducono un nome nello spazio di dichiarazione di un tipo. I costruttori possono essere richiamati solo da espressioni di creazione di oggetti o da .NET Framework; potrebbero non essere mai richiamati direttamente.

Nota. I costruttori hanno la stessa restrizione sulla posizione della riga a cui dispongono le subroutine. L'istruzione iniziale, l'istruzione end e il blocco devono essere tutti visualizzati all'inizio di una riga logica.

ConstructorMemberDeclaration
    : Attributes? ConstructorModifier* 'Sub' 'New'
      ( OpenParenthesis ParameterList? CloseParenthesis )? LineTerminator
      Block?
      'End' 'Sub' StatementTerminator
    ;

ConstructorModifier
    : AccessModifier
    | 'Shared'
    ;

Costruttori di istanze

I costruttori di istanza inizializzano le istanze di un tipo e vengono eseguiti da .NET Framework quando viene creata un'istanza. L'elenco di parametri di un costruttore è soggetto alle stesse regole dell'elenco di parametri di un metodo. È possibile che i costruttori di istanza siano in overload.

Tutti i costruttori nei tipi di riferimento devono richiamare un altro costruttore. Se la chiamata è esplicita, deve essere la prima istruzione nel corpo del metodo del costruttore. L'istruzione può richiamare un altro costruttore dell'istanza del tipo, ad esempio oppure Me.New(...)MyClass.New(...) , oppure se non è una struttura può richiamare un costruttore di istanza del tipo di base del tipo, MyBase.New(...)ad esempio . Non è valido per un costruttore per richiamare se stesso. Se un costruttore omette una chiamata a un altro costruttore, MyBase.New() è implicito. Se non è presente alcun costruttore di tipo di base senza parametri, si verifica un errore in fase di compilazione. Poiché Me non viene considerato come costruito fino a quando la chiamata a un costruttore di classe base, i parametri di un'istruzione di chiamata del costruttore non possono fare riferimento Mea , MyClasso MyBase in modo implicito o esplicito.

Quando la prima istruzione di un costruttore è nel formato MyBase.New(...), il costruttore esegue in modo implicito le inizializzazioni specificate dagli inizializzatori di variabile delle variabili di istanza dichiarate nel tipo . Corrisponde a una sequenza di assegnazioni eseguite immediatamente dopo aver richiamato il costruttore del tipo di base diretto. Tale ordinamento garantisce che tutte le variabili di istanza di base vengano inizializzate dai relativi inizializzatori di variabile prima che vengano eseguite istruzioni che hanno accesso all'istanza. Per esempio:

Class A
    Protected x As Integer = 1
End Class

Class B
    Inherits A

    Private y As Integer = x

    Public Sub New()
        Console.WriteLine("x = " & x & ", y = " & y)
    End Sub
End Class

Quando New B() viene usato per creare un'istanza di B, viene generato l'output seguente:

x = 1, y = 1

Il valore di è 1 dovuto al fatto che l'inizializzatore di y variabile viene eseguito dopo la chiamata del costruttore della classe di base. Gli inizializzatori di variabili vengono eseguiti nell'ordine testuale in cui vengono visualizzati nella dichiarazione di tipo.

Quando un tipo dichiara solo Private costruttori, non è possibile in generale che altri tipi derivino dal tipo o creino istanze del tipo. L'unica eccezione è tipi annidata all'interno del tipo. Private i costruttori vengono comunemente usati nei tipi che contengono solo Shared membri.

Se un tipo non contiene dichiarazioni del costruttore di istanza, viene fornito automaticamente un costruttore predefinito. Il costruttore predefinito richiama semplicemente il costruttore senza parametri del tipo di base diretto. Se il tipo di base diretto non dispone di un costruttore senza parametri accessibile, si verifica un errore in fase di compilazione. Il tipo di accesso dichiarato per il costruttore predefinito è Public a meno che il tipo non sia MustInherit, nel qual caso il costruttore predefinito è Protected.

Nota. L'accesso predefinito per il costruttore predefinito di un MustInherit tipo è Protected dovuto al fatto che MustInherit le classi non possono essere create direttamente. Non c'è quindi alcun punto per fare in modo che il costruttore Publicpredefinito .

Nell'esempio seguente viene fornito un costruttore predefinito perché la classe non contiene dichiarazioni di costruttore:

Class Message
    Dim sender As Object
    Dim text As String
End Class

Di conseguenza, l'esempio è esattamente equivalente al seguente:

Class Message
    Dim sender As Object
    Dim text As String

    Sub New()
    End Sub
End Class

I costruttori predefiniti generati in una classe generata dalla finestra di progettazione contrassegnata con l'attributo Microsoft.VisualBasic.CompilerServices.DesignerGeneratedAttribute chiameranno il metodo Sub InitializeComponent(), se presente, dopo la chiamata al costruttore di base. (Nota. Ciò consente alla finestra di progettazione di file generati, ad esempio quelli creati dalla finestra di progettazione WinForms, di omettere il costruttore nel file di progettazione. In questo modo il programmatore può specificarlo autonomamente, se lo sceglie.

Costruttori condivisi

I costruttori condivisi inizializzano le variabili condivise di un tipo; vengono eseguiti dopo l'avvio dell'esecuzione del programma, ma prima di qualsiasi riferimento a un membro del tipo. Un costruttore condiviso specifica il Shared modificatore, a meno che non si tratti di un modulo standard nel qual caso il Shared modificatore è implicito.

A differenza dei costruttori di istanze, i costruttori condivisi hanno accesso pubblico implicito, non hanno parametri e potrebbero non chiamare altri costruttori. Prima della prima istruzione in un costruttore condiviso, il costruttore condiviso esegue in modo implicito le inizializzazioni specificate dagli inizializzatori variabili delle variabili condivise dichiarate nel tipo . Corrisponde a una sequenza di assegnazioni eseguite immediatamente all'ingresso del costruttore. Gli inizializzatori di variabile vengono eseguiti nell'ordine testuale in cui vengono visualizzati nella dichiarazione di tipo.

L'esempio seguente mostra una Employee classe con un costruttore condiviso che inizializza una variabile condivisa:

Imports System.Data

Class Employee
    Private Shared ds As DataSet

    Shared Sub New()
        ds = New DataSet()
    End Sub

    Public Name As String
    Public Salary As Decimal
End Class

Esiste un costruttore condiviso separato per ogni tipo generico chiuso. Poiché il costruttore condiviso viene eseguito esattamente una volta per ogni tipo chiuso, è un'opzione utile applicare controlli di runtime sul parametro di tipo che non possono essere controllati in fase di compilazione tramite vincoli. Ad esempio, il tipo seguente usa un costruttore condiviso per imporre che il parametro di tipo sia Integer o Double:

Class EnumHolder(Of T)
    Shared Sub New() 
        If Not GetType(T).IsEnum() Then
            Throw New ArgumentException("T must be an enumerated type.")
        End If
    End Sub
End Class

Esattamente quando vengono eseguiti costruttori condivisi dipende principalmente dall'implementazione, anche se vengono fornite diverse garanzie se un costruttore condiviso è definito in modo esplicito:

  • I costruttori condivisi vengono eseguiti prima del primo accesso a qualsiasi campo statico del tipo.

  • I costruttori condivisi vengono eseguiti prima della prima chiamata di qualsiasi metodo statico del tipo.

  • I costruttori condivisi vengono eseguiti prima della prima chiamata di qualsiasi costruttore per il tipo.

Le garanzie precedenti non si applicano nella situazione in cui un costruttore condiviso viene creato in modo implicito per gli inizializzatori condivisi. L'output dell'esempio seguente è incerto, perché l'ordinamento esatto del caricamento e quindi dell'esecuzione del costruttore condiviso non è definito:

Module Test
    Sub Main()
        A.F()
        B.F()
    End Sub
End Module

Class A
    Shared Sub New()
        Console.WriteLine("Init A")
    End Sub

    Public Shared Sub F()
        Console.WriteLine("A.F")
    End Sub
End Class

Class B
    Shared Sub New()
        Console.WriteLine("Init B")
    End Sub

    Public Shared Sub F()
        Console.WriteLine("B.F")
    End Sub
End Class

L'output può essere uno dei seguenti:

Init A
A.F
Init B
B.F

o

Init B
Init A
A.F
B.F

L'esempio seguente genera invece un output prevedibile. Si noti che il Shared costruttore per la classe A non viene mai eseguito, anche se la classe B deriva da essa:

Module Test
    Sub Main()
        B.G()
    End Sub
End Module

Class A
    Shared Sub New()
        Console.WriteLine("Init A")
    End Sub
End Class

Class B
    Inherits A

    Shared Sub New()
        Console.WriteLine("Init B")
    End Sub

    Public Shared Sub G()
        Console.WriteLine("B.G")
    End Sub
End Class

L'output è il seguente:

Init B
B.G

È anche possibile costruire dipendenze circolari che consentono Shared l'osservazione delle variabili con inizializzatori variabili nel relativo stato di valore predefinito, come nell'esempio seguente:

Class A
    Public Shared X As Integer = B.Y + 1
End Class

Class B
    Public Shared Y As Integer = A.X + 1

    Shared Sub Main()
        Console.WriteLine("X = " & A.X & ", Y = " & B.Y)
    End Sub
End Class

In questo modo viene generato l'output:

X = 1, Y = 2

Per eseguire il Main metodo , il sistema carica innanzitutto la classe B. Il Shared costruttore della classe B procede per calcolare il valore iniziale di Y, che fa sì che la classe A venga caricata in modo ricorsivo perché viene fatto riferimento al valore di A.X . Il Shared costruttore della classe A a sua volta procede al calcolo del valore iniziale di Xe in questo modo recupera il valore predefinito di Y, che è zero. A.X viene quindi inizializzato in 1. Il processo di caricamento A viene quindi completato, restituendo al calcolo del valore iniziale di Y, il risultato del quale diventa 2.

Se invece il Main metodo fosse posizionato nella classe A, l'esempio avrebbe prodotto l'output seguente:

X = 2, Y = 1

Evitare riferimenti circolari negli Shared inizializzatori di variabili poiché in genere è impossibile determinare l'ordine in cui vengono caricati classi contenenti tali riferimenti.

Avvenimenti

Gli eventi vengono usati per notificare al codice una determinata occorrenza. Una dichiarazione di evento è costituita da un identificatore, un tipo delegato o un elenco di parametri e una clausola facoltativa Implements .

EventMemberDeclaration
    : RegularEventMemberDeclaration
    | CustomEventMemberDeclaration
    ;

RegularEventMemberDeclaration
    : Attributes? EventModifiers* 'Event'
      Identifier ParametersOrType ImplementsClause? StatementTerminator
    ;

InterfaceEventMemberDeclaration
    : Attributes? InterfaceEventModifiers* 'Event'
      Identifier ParametersOrType StatementTerminator
    ;

ParametersOrType
    : ( OpenParenthesis ParameterList? CloseParenthesis )?
    | 'As' NonArrayTypeName
    ;

EventModifiers
    : AccessModifier
    | 'Shadows'
    | 'Shared'
    ;

InterfaceEventModifiers
    : 'Shadows'
    ;

Se viene specificato un tipo delegato, il tipo delegato potrebbe non avere un tipo restituito. Se viene specificato un elenco di parametri, potrebbe non contenere Optional parametri o ParamArray . Il dominio di accessibilità dei tipi di parametro e/o del tipo delegato deve essere uguale a o a un superset di , il dominio di accessibilità dell'evento stesso. Gli eventi possono essere condivisi specificando il Shared modificatore.

Oltre al nome del membro aggiunto allo spazio di dichiarazione del tipo, una dichiarazione di evento dichiara in modo implicito diversi altri membri. Dato un evento denominato X, i membri seguenti vengono aggiunti allo spazio di dichiarazione:

  • Se il formato della dichiarazione è una dichiarazione di metodo, viene introdotta una classe delegato annidata denominata XEventHandler . La classe delegato annidata corrisponde alla dichiarazione del metodo e ha la stessa accessibilità dell'evento. Gli attributi nell'elenco dei parametri si applicano ai parametri della classe delegato.

  • Variabile Private di istanza tipizzata come delegato, denominata XEvent.

  • Due metodi denominati add_X e remove_X che non possono essere richiamati, sottoposti a override o sottoposti a overload.

Se un tipo tenta di dichiarare un nome che corrisponde a uno dei nomi precedenti, verrà generato un errore in fase di compilazione e le dichiarazioni e remove_X implicite add_X verranno ignorate ai fini dell'associazione di nomi. Non è possibile eseguire l'override o l'overload di uno dei membri introdotti, anche se è possibile eseguirne l'shadow nei tipi derivati. Ad esempio, la dichiarazione di classe

Class Raiser
    Public Event Constructed(i As Integer)
End Class

equivale alla dichiarazione seguente

Class Raiser
    Public Delegate Sub ConstructedEventHandler(i As Integer)

    Protected ConstructedEvent As ConstructedEventHandler

    Public Sub add_Constructed(d As ConstructedEventHandler)
        ConstructedEvent = _
            CType( _
                [Delegate].Combine(ConstructedEvent, d), _
                    Raiser.ConstructedEventHandler)
    End Sub

    Public Sub remove_Constructed(d As ConstructedEventHandler)
        ConstructedEvent = _
            CType( _
                [Delegate].Remove(ConstructedEvent, d), _
                    Raiser.ConstructedEventHandler)
    End Sub
End Class

La dichiarazione di un evento senza specificare un tipo delegato è la sintassi più semplice e compatta, ma presenta lo svantaggio di dichiarare un nuovo tipo delegato per ogni evento. Nell'esempio seguente, ad esempio, vengono creati tre tipi delegati nascosti, anche se tutti e tre gli eventi hanno lo stesso elenco di parametri:

Public Class Button
    Public Event Click(sender As Object, e As EventArgs)
    Public Event DoubleClick(sender As Object, e As EventArgs)
    Public Event RightClick(sender As Object, e As EventArgs)
End Class

Nell'esempio seguente gli eventi usano semplicemente lo stesso delegato, EventHandler:

Public Delegate Sub EventHandler(sender As Object, e As EventArgs)

Public Class Button
    Public Event Click As EventHandler
    Public Event DoubleClick As EventHandler
    Public Event RightClick As EventHandler
End Class

Gli eventi possono essere gestiti in uno dei due modi seguenti: staticamente o dinamicamente. La gestione statica degli eventi è più semplice e richiede solo una WithEvents variabile e una Handles clausola . Nell'esempio seguente la classe Form1 gestisce in modo statico l'evento Click dell'oggetto Button:

Public Class Form1
    Public WithEvents Button1 As New Button()

    Public Sub Button1_Click(sender As Object, e As EventArgs) _
           Handles Button1.Click
        Console.WriteLine("Button1 was clicked!")
    End Sub
End Class

La gestione dinamica degli eventi è più complessa perché l'evento deve essere connesso e disconnesso in modo esplicito nel codice. L'istruzione AddHandler aggiunge un gestore per un evento e l'istruzione RemoveHandler rimuove un gestore per un evento. L'esempio seguente mostra una classe Form1 che aggiunge Button1_Click come gestore eventi per Button1l'evento di Click :

Public Class Form1
    Public Sub New()
        ' Add Button1_Click as an event handler for Button1's Click event.
        AddHandler Button1.Click, AddressOf Button1_Click
    End Sub 

    Private Button1 As Button = New Button()

    Sub Button1_Click(sender As Object, e As EventArgs)
        Console.WriteLine("Button1 was clicked!")
    End Sub

    Public Sub Disconnect()
        RemoveHandler Button1.Click, AddressOf Button1_Click
    End Sub 
End Class

Nel metodo Disconnect, il gestore eventi viene rimosso.

Eventi personalizzati

Come illustrato nella sezione precedente, le dichiarazioni di eventi definiscono in modo implicito un campo, un add_ metodo e un remove_ metodo usati per tenere traccia dei gestori eventi. In alcune situazioni, tuttavia, potrebbe essere preferibile fornire codice personalizzato per il rilevamento dei gestori eventi. Ad esempio, se una classe definisce quaranta eventi di cui verranno gestiti solo alcuni, l'uso di una tabella hash anziché di quaranta campi per tenere traccia dei gestori per ogni evento potrebbe essere più efficiente. Gli eventi personalizzati consentono di definire i add_X metodi e remove_X in modo esplicito, che consente l'archiviazione personalizzata per i gestori eventi.

Gli eventi personalizzati vengono dichiarati nello stesso modo in cui gli eventi che specificano un tipo delegato vengono dichiarati, con l'eccezione che la parola chiave deve precedere la Event parola Custom chiave. Una dichiarazione di evento personalizzata contiene tre dichiarazioni: una AddHandler dichiarazione, una RemoveHandler dichiarazione e una RaiseEvent dichiarazione. Nessuna delle dichiarazioni può avere modificatori, anche se possono avere attributi.

CustomEventMemberDeclaration
    : Attributes? EventModifiers* 'Custom' 'Event'
      Identifier 'As' TypeName ImplementsClause? StatementTerminator
      EventAccessorDeclaration+
      'End' 'Event' StatementTerminator
    ;

EventAccessorDeclaration
    : AddHandlerDeclaration
    | RemoveHandlerDeclaration
    | RaiseEventDeclaration
    ;

AddHandlerDeclaration
    : Attributes? 'AddHandler'
      OpenParenthesis ParameterList CloseParenthesis LineTerminator
      Block?
      'End' 'AddHandler' StatementTerminator
    ;

RemoveHandlerDeclaration
    : Attributes? 'RemoveHandler'
      OpenParenthesis ParameterList CloseParenthesis LineTerminator
      Block?
      'End' 'RemoveHandler' StatementTerminator
    ;

RaiseEventDeclaration
    : Attributes? 'RaiseEvent'
      OpenParenthesis ParameterList CloseParenthesis LineTerminator
      Block?
      'End' 'RaiseEvent' StatementTerminator
    ;

Per esempio:

Class Test
    Private Handlers As EventHandler

    Public Custom Event TestEvent As EventHandler
        AddHandler(value As EventHandler)
            Handlers = CType([Delegate].Combine(Handlers, value), _
                EventHandler)
        End AddHandler

        RemoveHandler(value as EventHandler)
            Handlers = CType([Delegate].Remove(Handlers, value), _
                EventHandler)
        End RemoveHandler

        RaiseEvent(sender As Object, e As EventArgs)
            Dim TempHandlers As EventHandler = Handlers

            If TempHandlers IsNot Nothing Then
                TempHandlers(sender, e)
            End If
        End RaiseEvent
    End Event
End Class

La AddHandler dichiarazione e RemoveHandler accetta un ByVal parametro, che deve essere del tipo delegato dell'evento. Quando viene eseguita un'istruzione AddHandler o RemoveHandler (o una Handles clausola gestisce automaticamente un evento), verrà chiamata la dichiarazione corrispondente. La RaiseEvent dichiarazione accetta gli stessi parametri del delegato dell'evento e verrà chiamata quando viene eseguita un'istruzione RaiseEvent . Tutte le dichiarazioni devono essere fornite e sono considerate subroutine.

Si noti che AddHandlerle dichiarazioni e RemoveHandlerRaiseEvent hanno la stessa restrizione sulla posizione della riga che le subroutine hanno. L'istruzione iniziale, l'istruzione end e il blocco devono essere tutti visualizzati all'inizio di una riga logica.

Oltre al nome del membro aggiunto allo spazio di dichiarazione del tipo, una dichiarazione di evento personalizzata dichiara in modo implicito diversi altri membri. Dato un evento denominato X, i membri seguenti vengono aggiunti allo spazio di dichiarazione:

  • Metodo denominato add_X, corrispondente alla AddHandler dichiarazione.

  • Metodo denominato remove_X, corrispondente alla RemoveHandler dichiarazione.

  • Metodo denominato fire_X, corrispondente alla RaiseEvent dichiarazione.

Se un tipo tenta di dichiarare un nome corrispondente a uno dei nomi precedenti, verrà generato un errore in fase di compilazione e tutte le dichiarazioni implicite verranno ignorate ai fini dell'associazione di nomi. Non è possibile eseguire l'override o l'overload di uno dei membri introdotti, anche se è possibile eseguirne l'shadow nei tipi derivati.

Nota. Custom non è una parola riservata.

Eventi personalizzati negli assembly WinRT

A partire da Microsoft Visual Basic 11.0, gli eventi dichiarati in un file compilato con /target:winmdobjo dichiarati in un'interfaccia in tale file e quindi implementati altrove, vengono trattati in modo leggermente diverso.

  • Gli strumenti esterni usati per compilare winmd consentono in genere solo determinati tipi delegati, ad System.EventHandler(Of T) esempio o System.TypedEventHandle(Of T, U), e non consentono altri tipi.

  • Il XEvent campo ha il tipo System.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenTable(Of T) dove T è il tipo delegato.

  • La funzione di accesso AddHandler restituisce un System.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenoggetto e la funzione di accesso RemoveHandler accetta un singolo parametro dello stesso tipo.

Di seguito è riportato un esempio di evento personalizzato di questo tipo.

Imports System.Runtime.InteropServices.WindowsRuntime

Public NotInheritable Class ClassInWinMD
    Private XEvent As EventRegistrationTokenTable(Of EventHandler(Of Integer))

    Public Custom Event X As EventHandler(Of Integer)
        AddHandler(handler As EventHandler(Of Integer))
            Return EventRegistrationTokenTable(Of EventHandler(Of Integer)).
                   GetOrCreateEventRegistrationTokenTable(XEvent).
                   AddEventHandler(handler)
        End AddHandler

        RemoveHandler(token As EventRegistrationToken)
            EventRegistrationTokenTable(Of EventHandler(Of Integer)).
                GetOrCreateEventRegistrationTokenTable(XEvent).
                RemoveEventHandler(token)
        End RemoveHandler

        RaiseEvent(sender As Object, i As Integer)
            Dim table = EventRegistrationTokenTable(Of EventHandler(Of Integer)).
                GetOrCreateEventRegistrationTokenTable(XEvent).
                InvocationList
            If table IsNot Nothing Then table(sender, i)
        End RaiseEvent
    End Event
End Class

Costanti

Una costante è un valore costante che è un membro di un tipo.

ConstantMemberDeclaration
    : Attributes? ConstantModifier* 'Const' ConstantDeclarators StatementTerminator
    ;

ConstantModifier
    : AccessModifier
    | 'Shadows'
    ;

ConstantDeclarators
    : ConstantDeclarator ( Comma ConstantDeclarator )*
    ;

ConstantDeclarator
    : Identifier ( 'As' TypeName )? Equals ConstantExpression StatementTerminator
    ;

Le costanti vengono condivise in modo implicito. Se la dichiarazione contiene una As clausola , la clausola specifica il tipo del membro introdotto dalla dichiarazione. Se il tipo viene omesso, viene dedotto il tipo della costante. Il tipo di una costante può essere solo un tipo primitivo o Object. Se una costante viene tipizzata come Object e non è presente alcun carattere di tipo, il tipo reale della costante sarà il tipo dell'espressione costante. In caso contrario, il tipo della costante è il tipo del carattere di tipo della costante.

L'esempio seguente mostra una classe denominata Constants con due costanti pubbliche:

Class Constants
    Public Const A As Integer = 1
    Public Const B As Integer = A + 1
End Class

È possibile accedere alle costanti tramite la classe , come nell'esempio seguente, che stampa i valori di Constants.A e Constants.B.

Module Test
    Sub Main()
        Console.WriteLine(Constants.A & ", " & Constants.B)
    End Sub 
End Module

Una dichiarazione costante che dichiara più costanti equivale a più dichiarazioni di costanti singole. Nell'esempio seguente vengono dichiarate tre costanti in un'istruzione di dichiarazione.

Class A
    Protected Const x As Integer = 1, y As Long = 2, z As Short = 3
End Class

Questa dichiarazione equivale al seguente:

Class A
    Protected Const x As Integer = 1
    Protected Const y As Long = 2
    Protected Const z As Short = 3
End Class

Il dominio di accessibilità del tipo della costante deve essere uguale a o a un superset del dominio di accessibilità della costante stessa. L'espressione costante deve restituire un valore del tipo della costante o di un tipo convertibile in modo implicito nel tipo della costante. L'espressione costante potrebbe non essere circolare; ovvero, una costante potrebbe non essere definita in termini di se stessa.

Il compilatore valuta automaticamente le dichiarazioni costanti nell'ordine appropriato. Nell'esempio seguente il compilatore valuta Yprima , quindi Z, e infine X, producendo rispettivamente i valori 10, 11 e 12.

Class A
    Public Const X As Integer = B.Z + 1
    Public Const Y As Integer = 10
End Class

Class B
    Public Const Z As Integer = A.Y + 1
End Class

Quando si desidera un nome simbolico per un valore costante, ma il tipo del valore non è consentito in una dichiarazione costante o quando il valore non può essere calcolato in fase di compilazione da un'espressione costante, è possibile usare invece una variabile di sola lettura.

Variabili di istanza e condivise

Un'istanza o una variabile condivisa è un membro di un tipo in grado di archiviare informazioni.

VariableMemberDeclaration
    : Attributes? VariableModifier+ VariableDeclarators StatementTerminator
    ;

VariableModifier
    : AccessModifier
    | 'Shadows'
    | 'Shared'
    | 'ReadOnly'
    | 'WithEvents'
    | 'Dim'
    ;

VariableDeclarators
    : VariableDeclarator ( Comma VariableDeclarator )*
    ;

VariableDeclarator
    : VariableIdentifiers 'As' ObjectCreationExpression
    | VariableIdentifiers ( 'As' TypeName )? ( Equals Expression )?
    ;

VariableIdentifiers
    : VariableIdentifier ( Comma VariableIdentifier )*
    ;

VariableIdentifier
    : Identifier IdentifierModifiers
    ;

Il Dim modificatore deve essere specificato se non viene specificato alcun modificatore, ma può essere omesso in caso contrario. Una singola dichiarazione di variabile può includere più dichiaratori di variabili; ogni dichiaratore di variabile introduce una nuova istanza o un membro condiviso.

Se viene specificato un inizializzatore, è possibile dichiarare una sola istanza o variabile condivisa dal dichiaratore di variabile:

Class Test
    Dim a, b, c, d As Integer = 10  ' Invalid: multiple initialization
End Class

Questa restrizione non si applica agli inizializzatori di oggetto:

Class Test
    Dim a, b, c, d As New Collection() ' OK
End Class

Una variabile dichiarata con il Shared modificatore è una variabile condivisa. Una variabile condivisa identifica esattamente una posizione di archiviazione indipendentemente dal numero di istanze del tipo creato. Una variabile condivisa entra in vigore quando un programma inizia l'esecuzione e cessa di esistere quando il programma termina.

Una variabile condivisa viene condivisa solo tra istanze di un particolare tipo generico chiuso. Ad esempio, il programma:

Class C(Of V) 
    Shared InstanceCount As Integer = 0

    Public Sub New()  
        InstanceCount += 1 
    End Sub

    Public Shared ReadOnly Property Count() As Integer 
        Get
            Return InstanceCount
        End Get
    End Property
End Class

Class Application 
    Shared Sub Main() 
        Dim x1 As New C(Of Integer)()
        Console.WriteLine(C(Of Integer).Count)

        Dim x2 As New C(Of Double)() 
        Console.WriteLine(C(Of Integer).Count)

        Dim x3 As New C(Of Integer)() 
        Console.WriteLine(C(Of Integer).Count)
    End Sub
End Class

Stampa:

1
1
2

Una variabile dichiarata senza il Shared modificatore viene chiamata variabile di istanza. Ogni istanza di una classe contiene una copia separata di tutte le variabili di istanza della classe . Una variabile di istanza di un tipo riferimento è presente quando viene creata una nuova istanza di tale tipo e cessa di esistere quando non sono presenti riferimenti a tale istanza e il Finalize metodo è stato eseguito. Una variabile di istanza di un tipo valore ha esattamente la stessa durata della variabile a cui appartiene. In altre parole, quando una variabile di un tipo valore entra in esistenza o cessa di esistere, in modo che la variabile di istanza del tipo di valore.

Se il dichiaratore contiene una As clausola , la clausola specifica il tipo dei membri introdotti dalla dichiarazione. Se il tipo viene omesso e viene usata una semantica rigorosa, si verifica un errore in fase di compilazione. In caso contrario, il tipo dei membri è implicito Object o il tipo del carattere di tipo dei membri.

Nota. Non esiste ambiguità nella sintassi: se un dichiaratore omette un tipo, userà sempre il tipo di un dichiaratore seguente.

Il dominio di accessibilità del tipo o dell'elemento matrice di un'istanza o di una variabile condivisa deve essere uguale a o a un superset del dominio di accessibilità dell'istanza o della variabile condivisa stessa.

L'esempio seguente mostra una Color classe con variabili di istanza interne denominate redPart, greenParte bluePart:

Class Color
    Friend redPart As Short
    Friend bluePart As Short
    Friend greenPart As Short

    Public Sub New(red As Short, blue As Short, green As Short)
        redPart = red
        bluePart = blue
        greenPart = green
    End Sub
End Class

Variabili Read-Only

Quando una dichiarazione di istanza o variabile condivisa include un ReadOnly modificatore, le assegnazioni alle variabili introdotte dalla dichiarazione possono verificarsi solo come parte della dichiarazione o in un costruttore nella stessa classe. In particolare, le assegnazioni a un'istanza di sola lettura o a una variabile condivisa sono consentite solo nelle situazioni seguenti:

  • Nella dichiarazione di variabile che introduce l'istanza o la variabile condivisa (includendo un inizializzatore di variabile nella dichiarazione).

  • Per una variabile di istanza, nei costruttori di istanza della classe che contiene la dichiarazione di variabile. È possibile accedere alla variabile di istanza solo in modo non qualificato o tramite Me o MyClass.

  • Per una variabile condivisa, nel costruttore condiviso della classe che contiene la dichiarazione di variabile condivisa.

Una variabile di sola lettura condivisa è utile quando si desidera un nome simbolico per un valore costante, ma quando il tipo del valore non è consentito in una dichiarazione costante o quando il valore non può essere calcolato in fase di compilazione da un'espressione costante.

Di seguito è riportato un esempio della prima applicazione, in cui le variabili condivise a colori vengono dichiarate ReadOnly per impedirne la modifica da parte di altri programmi:

Class Color
    Friend redPart As Short
    Friend bluePart As Short
    Friend greenPart As Short

    Public Sub New(red As Short, blue As Short, green As Short)
        redPart = red
        bluePart = blue
        greenPart = green
    End Sub 

    Public Shared ReadOnly Red As Color = New Color(&HFF, 0, 0)
    Public Shared ReadOnly Blue As Color = New Color(0, &HFF, 0)
    Public Shared ReadOnly Green As Color = New Color(0, 0, &HFF)
    Public Shared ReadOnly White As Color = New Color(&HFF, &HFF, &HFF)
End Class

Le costanti e le variabili condivise di sola lettura hanno semantica diversa. Quando un'espressione fa riferimento a una costante, il valore della costante viene ottenuto in fase di compilazione, ma quando un'espressione fa riferimento a una variabile condivisa di sola lettura, il valore della variabile condivisa non viene ottenuto fino al runtime. Si consideri l'applicazione seguente, costituita da due programmi separati.

file1.vb:

Namespace Program1
    Public Class Utils
        Public Shared ReadOnly X As Integer = 1
    End Class
End Namespace

file2.vb:

Namespace Program2
    Module Test
        Sub Main()
            Console.WriteLine(Program1.Utils.X)
        End Sub
    End Module
End Namespace

Gli spazi dei nomi e Program2 indicano due programmi compilati Program1 separatamente. Poiché la variabile Program1.Utils.X è dichiarata come Shared ReadOnly, l'output del valore dell'istruzione Console.WriteLine non è noto in fase di compilazione, ma viene ottenuto in fase di esecuzione. Pertanto, se il valore di X viene modificato e Program1 ricompilato, l'istruzione Console.WriteLine restituirà il nuovo valore anche se Program2 non viene ricompilato. Tuttavia, se X fosse stata una costante, il valore di X sarebbe stato ottenuto al momento Program2 della compilazione e sarebbe rimasto invariato dalle modifiche apportate Program1 fino a quando Program2 non è stato ricompilato.

Variabili WithEvents

Un tipo può dichiarare che gestisce alcuni set di eventi generati da una delle relative variabili di istanza o condivise dichiarando l'istanza o la variabile condivisa che genera gli eventi con il WithEvents modificatore. Per esempio:

Class Raiser
    Public Event E1()

    Public Sub Raise()
        RaiseEvent E1
    End Sub
End Class

Module Test
    Private WithEvents x As Raiser

    Private Sub E1Handler() Handles x.E1
        Console.WriteLine("Raised")
    End Sub

    Public Sub Main()
        x = New Raiser()
    End Sub
End Module

In questo esempio il metodo E1Handler gestisce l'evento E1 generato dall'istanza del tipo Raiser archiviato nella variabile xdi istanza .

Il WithEvents modificatore fa sì che la variabile venga rinominata con un carattere di sottolineatura iniziale e sostituita con una proprietà con lo stesso nome che esegue l'hookup dell'evento. Ad esempio, se il nome della variabile è F, viene rinominato in _F e una proprietà F viene dichiarata in modo implicito. Se si verifica un conflitto tra il nuovo nome della variabile e un'altra dichiarazione, verrà segnalato un errore in fase di compilazione. Tutti gli attributi applicati alla variabile vengono trasportati alla variabile rinominata.

La proprietà implicita creata da una WithEvents dichiarazione si occupa dell'hooking e dell'annullamento delhoo dei gestori eventi pertinenti. Quando un valore viene assegnato alla variabile, la proprietà chiama innanzitutto il remove metodo per l'evento nell'istanza attualmente presente nella variabile (scollegando il gestore eventi esistente, se presente). Successivamente viene eseguita l'assegnazione e la proprietà chiama il add metodo per l'evento nella nuova istanza della variabile (associando il nuovo gestore eventi). Il codice seguente equivale al codice precedente per il modulo Teststandard :

Module Test
    Private _x As Raiser

    Public Property x() As Raiser
        Get
            Return _x
        End Get

        Set (Value As Raiser)
            ' Unhook any existing handlers.
            If _x IsNot Nothing Then
                RemoveHandler _x.E1, AddressOf E1Handler
            End If

            ' Change value.
            _x = Value

            ' Hook-up new handlers.
            If _x IsNot Nothing Then
                AddHandler _x.E1, AddressOf E1Handler
            End If
        End Set
    End Property

    Sub E1Handler()
        Console.WriteLine("Raised")
    End Sub

    Sub Main()
        x = New Raiser()
    End Sub
End Module

Non è valido dichiarare un'istanza o una variabile condivisa come WithEvents se la variabile fosse tipizzata come struttura. Inoltre, WithEvents non può essere specificato in una struttura e WithEventsReadOnly non può essere combinato.

Inizializzatori di variabili

Le dichiarazioni di istanza e variabili condivise nelle classi e nelle dichiarazioni di variabili di istanza (ma non dichiarazioni di variabili condivise) nelle strutture possono includere inizializzatori di variabili. Per Shared le variabili, gli inizializzatori di variabili corrispondono alle istruzioni di assegnazione eseguite dopo l'inizio del programma, ma prima che venga fatto riferimento alla Shared variabile. Per le variabili di istanza, gli inizializzatori di variabili corrispondono alle istruzioni di assegnazione eseguite quando viene creata un'istanza della classe . Le strutture non possono avere inizializzatori di variabili di istanza perché non è possibile modificare i costruttori senza parametri.

Si consideri l'esempio seguente:

Class Test
    Public Shared x As Double = Math.Sqrt(2.0)
    Public i As Integer = 100
    Public s As String = "Hello"
End Class

Module TestModule
    Sub Main()
        Dim a As New Test()

        Console.WriteLine("x = " & Test.x & ", i = " & a.i & ", s = " & a.s)
    End Sub
End Module

L'esempio produce l'output seguente:

x = 1.4142135623731, i = 100, s = Hello

Un'assegnazione a x si verifica quando viene caricata la classe e le assegnazioni a i e s si verificano quando viene creata una nuova istanza della classe .

È utile considerare gli inizializzatori di variabile come istruzioni di assegnazione che vengono inseriti automaticamente nel blocco del costruttore del tipo. L'esempio seguente contiene diversi inizializzatori di variabili di istanza.

Class A
    Private x As Integer = 1
    Private y As Integer = -1
    Private count As Integer

    Public Sub New()
        count = 0
    End Sub

    Public Sub New(n As Integer)
        count = n
    End Sub
End Class

Class B
    Inherits A

    Private sqrt2 As Double = Math.Sqrt(2.0)
    Private items As ArrayList = New ArrayList(100)
    Private max As Integer

    Public Sub New()
        Me.New(100)
        items.Add("default")
    End Sub

    Public Sub New(n As Integer)
        MyBase.New(n - 1)
        max = n
    End Sub
End Class

L'esempio corrisponde al codice illustrato di seguito, dove ogni commento indica un'istruzione inserita automaticamente.

Class A
    Private x, y, count As Integer

    Public Sub New()
        MyBase.New ' Invoke object() constructor.
        x = 1 ' This is a variable initializer.
        y = -1 ' This is a variable initializer.
        count = 0
    End Sub

    Public Sub New(n As Integer)
        MyBase.New ' Invoke object() constructor. 
        x = 1 ' This is a variable initializer.
        y = - 1 ' This is a variable initializer.
        count = n
    End Sub
End Class

Class B
    Inherits A

    Private sqrt2 As Double
    Private items As ArrayList
    Private max As Integer

    Public Sub New()
        Me.New(100) 
        items.Add("default")
    End Sub

    Public Sub New(n As Integer)
        MyBase.New(n - 1) 
        sqrt2 = Math.Sqrt(2.0) ' This is a variable initializer.
        items = New ArrayList(100) ' This is a variable initializer.
        max = n
    End Sub
End Class

Tutte le variabili vengono inizializzate sul valore predefinito del tipo prima dell'esecuzione di qualsiasi inizializzatore di variabile. Per esempio:

Class Test
    Public Shared b As Boolean
    Public i As Integer
End Class

Module TestModule
    Sub Main()
        Dim t As New Test()
        Console.WriteLine("b = " & Test.b & ", i = " & t.i)
    End Sub
End Module

Poiché b viene inizializzato automaticamente sul valore predefinito quando la classe viene caricata e i viene inizializzata automaticamente sul valore predefinito quando viene creata un'istanza della classe, il codice precedente genera l'output seguente:

b = False, i = 0

Ogni inizializzatore di variabile deve restituire un valore del tipo della variabile o di un tipo convertibile in modo implicito nel tipo della variabile. Un inizializzatore di variabile può essere circolare o fare riferimento a una variabile che verrà inizializzata dopo di essa, nel qual caso il valore della variabile a cui si fa riferimento è il valore predefinito ai fini dell'inizializzatore. Tale inizializzatore è di valore dubbio.

Esistono tre forme di inizializzatori variabili: inizializzatori regolari, inizializzatori di dimensioni di matrice e inizializzatori di oggetti. Le prime due forme vengono visualizzate dopo un segno di uguale che segue il nome del tipo, le ultime due fanno parte della dichiarazione stessa. In qualsiasi dichiarazione specifica può essere utilizzata una sola forma di inizializzatore.

Inizializzatori regolari

Un inizializzatore regolare è un'espressione convertibile in modo implicito nel tipo della variabile. Viene visualizzato dopo un segno di uguale che segue il nome del tipo e deve essere classificato come valore. Per esempio:

Module Test
    Dim x As Integer = 10
    Dim y As Integer = 20

    Sub Main()
        Console.WriteLine("x = " & x & ", y = " & y)
    End Sub
End Module

Questo programma produce l'output:

x = 10, y = 20

Se una dichiarazione di variabile ha un inizializzatore regolare, è possibile dichiarare solo una singola variabile alla volta. Per esempio:

Module Test
    Sub Main()
        ' OK, only one variable declared at a time.
        Dim x As Integer = 10, y As Integer = 20

        ' Error: Can't initialize multiple variables at once.
        Dim a, b As Integer = 10
    End Sub
End Module

Inizializzatori di oggetti

Un inizializzatore di oggetto viene specificato utilizzando un'espressione di creazione di oggetti al posto del nome del tipo. Un inizializzatore di oggetto equivale a un inizializzatore regolare che assegna il risultato dell'espressione di creazione dell'oggetto alla variabile. So

Module TestModule
    Sub Main()
        Dim x As New Test(10)
    End Sub
End Module

equivale a

Module TestModule
    Sub Main()
        Dim x As Test = New Test(10)
    End Sub
End Module

Le parentesi in un inizializzatore di oggetto vengono sempre interpretate come l'elenco di argomenti per il costruttore e mai come modificatori di tipi di matrice. Un nome di variabile con un inizializzatore di oggetto non può avere un modificatore di tipo matrice o un modificatore di tipo nullable.

inizializzatori Array-Size

Un inizializzatore di dimensioni di matrice è un modificatore nel nome della variabile che fornisce un set di limiti superiori della dimensione indicati dalle espressioni.

ArraySizeInitializationModifier
    : OpenParenthesis BoundList CloseParenthesis ArrayTypeModifiers?
    ;

BoundList
    : Bound ( Comma Bound )*
    ;

Bound
    : Expression
    | '0' 'To' Expression
    ;

Le espressioni con limite superiore devono essere classificate come valori e devono essere convertibili in modo implicito in Integer. Il set di limiti superiori equivale a un inizializzatore di variabile di un'espressione di creazione di matrice con i limiti superiori specificati. Il numero di dimensioni del tipo di matrice viene dedotto dall'inizializzatore di dimensioni della matrice. So

Module Test
    Sub Main()
        Dim x(5, 10) As Integer
    End Sub
End Module

equivale a

Module Test
    Sub Main()
        Dim x As Integer(,) = New Integer(5, 10) {}
    End Sub
End Module

Tutti i limiti superiori devono essere uguali o maggiori di -1 e tutte le dimensioni devono avere un limite superiore specificato. Se il tipo di elemento della matrice inizializzata è un tipo di matrice, i modificatori di tipo matrice passano a destra dell'inizializzatore di dimensioni della matrice. Per esempio

Module Test
    Sub Main()
        Dim x(5,10)(,,) As Integer
    End Sub
End Module

dichiara una variabile x locale il cui tipo è una matrice bidimensionale di matrici tridimensionali di Integer, inizializzata in una matrice con limiti di 0..5 nella prima dimensione e 0..10 nella seconda dimensione. Non è possibile usare un inizializzatore di dimensioni della matrice per inizializzare gli elementi di una variabile il cui tipo è una matrice di matrici.

Una dichiarazione di variabile con un inizializzatore di dimensioni di matrice non può avere un modificatore di tipo matrice nel tipo o in un inizializzatore normale.

Classi System.MarshalByRefObject

Le classi che derivano dalla classe System.MarshalByRefObject vengono sottoposte a marshalling tra i limiti del contesto usando proxy ,ovvero per riferimento, anziché tramite la copia (ovvero, per valore). Ciò significa che un'istanza di tale classe potrebbe non essere un'istanza vera, ma può essere semplicemente uno stub che effettua il marshalling di accessi a variabili e chiamate al metodo attraverso un limite di contesto.

Di conseguenza, non è possibile creare un riferimento alla posizione di archiviazione delle variabili definite in tali classi. Ciò significa che le variabili tipate come classi derivate da System.MarshalByRefObject non possono essere passate ai parametri di riferimento e i metodi e le variabili delle variabili tipate come tipi valore potrebbero non essere accessibili. Visual Basic considera invece le variabili definite in tali classi come se fossero proprietà (poiché le restrizioni sono le stesse sulle proprietà).

Esiste un'eccezione a questa regola: un membro qualificato in modo implicito o esplicito con Me è esente dalle restrizioni precedenti, perché Me è sempre garantito che sia un oggetto effettivo, non un proxy.

Proprietà

Le proprietà sono un'estensione naturale delle variabili; entrambi sono membri denominati con tipi associati e la sintassi per l'accesso a variabili e proprietà è la stessa. A differenza delle variabili, tuttavia, le proprietà non indicano i percorsi di archiviazione. Le proprietà invece dispongono di funzioni di accesso, che specificano le istruzioni da eseguire per leggere o scrivere i relativi valori.

Le proprietà vengono definite con dichiarazioni di proprietà. La prima parte di una dichiarazione di proprietà è simile a una dichiarazione di campo. La seconda parte include una Get funzione di accesso e/o una Set funzione di accesso.

PropertyMemberDeclaration
    : RegularPropertyMemberDeclaration
    | MustOverridePropertyMemberDeclaration
    | AutoPropertyMemberDeclaration
    ;

PropertySignature
    : 'Property'
      Identifier ( OpenParenthesis ParameterList? CloseParenthesis )?
      ( 'As' Attributes? TypeName )?
    ;

RegularPropertyMemberDeclaration
    : Attributes? PropertyModifier* PropertySignature
      ImplementsClause? LineTerminator
      PropertyAccessorDeclaration+
      'End' 'Property' StatementTerminator
    ;

MustOverridePropertyMemberDeclaration
    : Attributes? MustOverridePropertyModifier+ PropertySignature
      ImplementsClause? StatementTerminator
    ;

AutoPropertyMemberDeclaration
    : Attributes? AutoPropertyModifier* 'Property' Identifier
      ( OpenParenthesis ParameterList? CloseParenthesis )?
      ( 'As' Attributes? TypeName )? ( Equals Expression )?
      ImplementsClause? LineTerminator
    | Attributes? AutoPropertyModifier* 'Property' Identifier
      ( OpenParenthesis ParameterList? CloseParenthesis )?
      'As' Attributes? 'New'
      ( NonArrayTypeName ( OpenParenthesis ArgumentList? CloseParenthesis )? )?
      ObjectCreationExpressionInitializer?
      ImplementsClause? LineTerminator
    ;

InterfacePropertyMemberDeclaration
    : Attributes? InterfacePropertyModifier* PropertySignature StatementTerminator
    ;

AutoPropertyModifier
    : AccessModifier
    | 'Shadows'
    | 'Shared'
    | 'Overridable'
    | 'NotOverridable'
    | 'Overrides'
    | 'Overloads'
    ;

PropertyModifier
    : AutoPropertyModifier
    | 'Default'
    | 'ReadOnly'
    | 'WriteOnly'
    | 'Iterator'
    ;

MustOverridePropertyModifier
    : PropertyModifier
    | 'MustOverride'
    ;

InterfacePropertyModifier
    : 'Shadows'
    | 'Overloads'
    | 'Default'
    | 'ReadOnly'
    | 'WriteOnly'
    ;

PropertyAccessorDeclaration
    : PropertyGetDeclaration
    | PropertySetDeclaration
    ;

Nell'esempio seguente la Button classe definisce una Caption proprietà .

Public Class Button
    Private captionValue As String

    Public Property Caption() As String
        Get
            Return captionValue
        End Get

        Set (Value As String)
            captionValue = value
            Repaint()
        End Set
    End Property

    ...
End Class

In base alla Button classe precedente, di seguito è riportato un esempio di utilizzo della Caption proprietà :

Dim okButton As Button = New Button()

okButton.Caption = "OK" ' Invokes Set accessor.
Dim s As String = okButton.Caption ' Invokes Get accessor.

In questo caso, la Set funzione di accesso viene richiamata assegnando un valore alla proprietà e la Get funzione di accesso viene richiamata facendo riferimento alla proprietà in un'espressione.

Se non viene specificato alcun tipo per una proprietà e viene utilizzata una semantica rigorosa, si verifica un errore in fase di compilazione; in caso contrario, il tipo della proprietà è implicito Object o il tipo del carattere di tipo della proprietà. Una dichiarazione di proprietà può contenere una Get funzione di accesso, che recupera il valore della proprietà, una Set funzione di accesso, che archivia il valore della proprietà o entrambi. Poiché una proprietà dichiara in modo implicito i metodi, una proprietà può essere dichiarata con gli stessi modificatori di un metodo. Se la proprietà è definita in un'interfaccia o definita con il MustOverride modificatore, il corpo della proprietà e il End costrutto devono essere omessi; in caso contrario, si verifica un errore in fase di compilazione.

L'elenco dei parametri di indice costituisce la firma della proprietà, pertanto le proprietà possono essere sottoposte a overload sui parametri di indice, ma non sul tipo della proprietà. L'elenco di parametri di indice è uguale a quello di un metodo regolare. Tuttavia, nessuno dei parametri può essere modificato con il ByRef modificatore e nessuno di essi può essere denominato Value (che è riservato per il parametro valore implicito nella Set funzione di accesso).

Una proprietà può essere dichiarata come segue:

  • Se la proprietà non specifica alcun modificatore del tipo di proprietà, la proprietà deve disporre di una Get funzione di accesso e di una Set funzione di accesso. Si dice che la proprietà sia una proprietà di lettura/scrittura.

  • Se la proprietà specifica il ReadOnly modificatore, la proprietà deve avere una Get funzione di accesso e potrebbe non avere una Set funzione di accesso. La proprietà è detta proprietà di sola lettura. È un errore in fase di compilazione avere una proprietà di sola lettura come destinazione di un'assegnazione.

  • Se la proprietà specifica il WriteOnly modificatore, la proprietà deve avere una Set funzione di accesso e potrebbe non avere una Get funzione di accesso. La proprietà è detta proprietà di sola scrittura. Si tratta di un errore in fase di compilazione per fare riferimento a una proprietà di sola scrittura in un'espressione tranne che come destinazione di un'assegnazione o come argomento di un metodo.

Le Get funzioni di accesso e Set di una proprietà non sono membri distinti e non è possibile dichiarare separatamente le funzioni di accesso di una proprietà. Nell'esempio seguente non viene dichiarata una singola proprietà di lettura/scrittura. Dichiara invece due proprietà con lo stesso nome, una sola lettura e una sola scrittura:

Class A
    Private nameValue As String

    ' Error, contains a duplicate member name.
    Public ReadOnly Property Name() As String 
        Get
            Return nameValue
        End Get
    End Property

    ' Error, contains a duplicate member name.
    Public WriteOnly Property Name() As String 
        Set (Value As String)
            nameValue = value
        End Set
    End Property
End Class

Poiché due membri dichiarati nella stessa classe non possono avere lo stesso nome, nell'esempio viene generato un errore in fase di compilazione.

Per impostazione predefinita, l'accessibilità delle funzioni di accesso e Set di Get una proprietà corrisponde all'accessibilità della proprietà stessa. Tuttavia, le Get funzioni di accesso e Set possono anche specificare l'accessibilità separatamente dalla proprietà . In tal caso, l'accessibilità di una funzione di accesso deve essere più restrittiva rispetto all'accessibilità della proprietà e una sola funzione di accesso può avere un livello di accessibilità diverso rispetto alla proprietà . I tipi di accesso sono considerati più o meno restrittivi come segue:

  • Private è più restrittivo di Public, Protected Friend, Protectedo Friend.

  • Friend è più restrittivo di Protected Friend o Public.

  • Protected è più restrittivo di Protected Friend o Public.

  • Protected Friend è più restrittivo di Public.

Quando una delle funzioni di accesso di una proprietà è accessibile, ma l'altra non è , la proprietà viene considerata come se fosse di sola lettura o di sola scrittura. Per esempio:

Class A
    Public Property P() As Integer
        Get
            ...
        End Get

        Private Set (Value As Integer)
            ...
        End Set
    End Property
End Class

Module Test
    Sub Main()
        Dim a As A = New A()

        ' Error: A.P is read-only in this context.
        a.P = 10
    End Sub
End Module

Quando un tipo derivato nasconde una proprietà, la proprietà derivata nasconde la proprietà ombreggiata rispetto alla lettura e alla scrittura. Nell'esempio seguente la P proprietà in B nasconde la P proprietà rispetto A alla lettura e alla scrittura:

Class A
    Public WriteOnly Property P() As Integer
        Set (Value As Integer)
        End Set
    End Property
End Class

Class B
    Inherits A

    Public Shadows ReadOnly Property P() As Integer
       Get
       End Get
    End Property
End Class

Module Test
    Sub Main()
        Dim x As B = New B

        B.P = 10     ' Error, B.P is read-only.
    End Sub
End Module

Il dominio di accessibilità del tipo restituito o dei tipi di parametro deve essere uguale a o a un superset del dominio di accessibilità della proprietà stessa. Una proprietà può avere una Set sola funzione di accesso e una Get sola funzione di accesso.

Ad eccezione delle differenze nella sintassi di dichiarazione e chiamata, le Overridableproprietà , , MustOverrideNotOverridableOverrides, e MustInherit si comportano esattamente come Overridablei metodi , OverridesNotOverridable, MustOverride, e .MustInherit Quando viene eseguito l'override di una proprietà, la proprietà di override deve essere dello stesso tipo (sola lettura, sola lettura e scrittura). Una Overridable proprietà non può contenere una Private funzione di accesso.

Nell'esempio X seguente è una Overridable proprietà di sola lettura, Y è una Overridable proprietà di lettura/scrittura ed Z è una MustOverride proprietà di lettura/scrittura.

MustInherit Class A
    Private _y As Integer

    Public Overridable ReadOnly Property X() As Integer
        Get
            Return 0
        End Get
    End Property

    Public Overridable Property Y() As Integer
        Get
            Return _y
         End Get
        Set (Value As Integer)
            _y = value
        End Set
    End Property

    Public MustOverride Property Z() As Integer
End Class

Poiché Z è MustOverride, la classe A contenitore deve essere dichiarata MustInherit.

Al contrario, una classe che deriva dalla classe A è illustrata di seguito:

Class B
    Inherits A

    Private _z As Integer

    Public Overrides ReadOnly Property X() As Integer
        Get
            Return MyBase.X + 1
        End Get
    End Property

    Public Overrides Property Y() As Integer
        Get
            Return MyBase.Y
        End Get
        Set (Value As Integer)
            If value < 0 Then
                MyBase.Y = 0
            Else
                MyBase.Y = Value
            End If
        End Set
    End Property

    Public Overrides Property Z() As Integer
        Get
            Return _z
        End Get
        Set (Value As Integer)
            _z = Value
        End Set
    End Property
End Class

In questo caso, le dichiarazioni delle proprietà XYed Z eseguono l'override delle proprietà di base. Ogni dichiarazione di proprietà corrisponde esattamente ai modificatori di accessibilità, al tipo e al nome della proprietà ereditata corrispondente. La Get funzione di accesso della proprietà X e la Set funzione di accesso della proprietà Y usano la MyBase parola chiave per accedere alle proprietà ereditate. La dichiarazione di proprietà esegue l'override della proprietà Z , pertanto non esistono membri in sospeso MustOverride nella classe Be B può essere una classe regolareMustOverride.

Le proprietà possono essere usate per ritardare l'inizializzazione di una risorsa fino al momento in cui viene fatto riferimento per la prima volta. Per esempio:

Imports System.IO

Public Class ConsoleStreams
    Private Shared reader As TextReader
    Private Shared writer As TextWriter
    Private Shared errors As TextWriter

    Public Shared ReadOnly Property [In]() As TextReader
        Get
            If reader Is Nothing Then
                reader = Console.In
            End If
            Return reader
        End Get
    End Property

    Public Shared ReadOnly Property Out() As TextWriter
        Get
            If writer Is Nothing Then
                writer = Console.Out
            End If
            Return writer
        End Get
    End Property

    Public Shared ReadOnly Property [Error]() As TextWriter
        Get
            If errors Is Nothing Then
                errors = Console.Error
            End If
            Return errors
        End Get
    End Property
End Class

La ConsoleStreams classe contiene tre proprietà, In, Oute Error, che rappresentano rispettivamente i dispositivi di input, output ed errore standard. Esponendo questi membri come proprietà, la classe ConsoleStreams può ritardare la loro inizializzazione fino a quando non vengono effettivamente usati. Ad esempio, al primo riferimento alla Out proprietà , come in ConsoleStreams.Out.WriteLine("hello, world"), l'oggetto sottostante TextWriter per il dispositivo di output viene inizializzato. Tuttavia, se l'applicazione non fa riferimento alle In proprietà e Error , non vengono creati oggetti per tali dispositivi.

Ottenere le dichiarazioni delle funzioni di accesso

Una Get funzione di accesso (getter) viene dichiarata tramite una dichiarazione di proprietà Get . Una dichiarazione di proprietà Get è costituita dalla parola chiave Get seguita da un blocco di istruzioni. Data una proprietà denominata P, una Get dichiarazione della funzione di accesso dichiara in modo implicito un metodo con il nome get_P con gli stessi modificatori, tipo ed elenco di parametri della proprietà . Se il tipo contiene una dichiarazione con tale nome, viene restituito un errore in fase di compilazione, ma la dichiarazione implicita viene ignorata ai fini dell'associazione di nomi.

Una variabile locale speciale, dichiarata in modo implicito nello Get spazio di dichiarazione del corpo della funzione di accesso con lo stesso nome della proprietà, rappresenta il valore restituito della proprietà. La variabile locale ha una semantica di risoluzione dei nomi speciale se usata nelle espressioni. Se la variabile locale viene usata in un contesto che prevede un'espressione classificata come gruppo di metodi, ad esempio un'espressione di chiamata, il nome viene risolto nella funzione anziché nella variabile locale. Per esempio:

ReadOnly Property F(i As Integer) As Integer
    Get
        If i = 0 Then
            F = 1    ' Sets the return value.
        Else
            F = F(i - 1) ' Recursive call.
        End If
    End Get
End Property

L'uso delle parentesi può causare situazioni ambigue, ad esempio F(1) dove F è una proprietà il cui tipo è una matrice unidimensionale. In tutte le situazioni ambigue, il nome viene risolto nella proprietà anziché nella variabile locale. Per esempio:

ReadOnly Property F(i As Integer) As Integer()
    Get
        If i = 0 Then
            F = new Integer(2) { 1, 2, 3 }
        Else
            F = F(i - 1) ' Recursive call, not index.
        End If
    End Get
End Property

Quando il flusso di controllo lascia il corpo della Get funzione di accesso, il valore della variabile locale viene passato all'espressione di chiamata. Poiché la chiamata di una Get funzione di accesso è concettualmente equivalente alla lettura del valore di una variabile, è considerato uno stile di programmazione non valido per le funzioni di accesso per Get avere effetti collaterali osservabili, come illustrato nell'esempio seguente:

Class Counter
    Private Value As Integer

    Public ReadOnly Property NextValue() As Integer
        Get
            Value += 1
            Return Value
        End Get
    End Property
End Class

Il valore della NextValue proprietà dipende dal numero di volte in cui è stato eseguito l'accesso alla proprietà in precedenza. Pertanto, l'accesso alla proprietà produce un effetto collaterale osservabile e la proprietà deve essere implementata come metodo.

La convenzione "nessun effetto collaterale" per Get le funzioni di accesso non significa che Get le funzioni di accesso devono sempre essere scritte per restituire semplicemente valori archiviati nelle variabili. Infatti, Get le funzioni di accesso spesso calcolano il valore di una proprietà accedendo a più variabili o richiamando metodi. Tuttavia, una funzione di accesso progettata Get correttamente non esegue azioni che causano modifiche osservabili nello stato dell'oggetto.

Nota. Get le funzioni di accesso hanno la stessa restrizione sulla posizione della riga a cui dispongono le subroutine. L'istruzione iniziale, l'istruzione end e il blocco devono essere tutti visualizzati all'inizio di una riga logica.

PropertyGetDeclaration
    : Attributes? AccessModifier? 'Get' LineTerminator
      Block?
      'End' 'Get' StatementTerminator
    ;

Impostare le dichiarazioni delle funzioni di accesso

Una Set funzione di accesso (setter) viene dichiarata tramite una dichiarazione di set di proprietà. Una dichiarazione di set di proprietà è costituita dalla parola chiave Set, da un elenco di parametri facoltativi e da un blocco di istruzioni. Data una proprietà denominata P, una dichiarazione di setter dichiara in modo implicito un metodo con il nome set_P con gli stessi modificatori ed elenco di parametri della proprietà. Se il tipo contiene una dichiarazione con tale nome, viene restituito un errore in fase di compilazione, ma la dichiarazione implicita viene ignorata ai fini dell'associazione di nomi.

Se viene specificato un elenco di parametri, deve avere un membro, tale membro non deve avere modificatori ad eccezione ByValdi e il relativo tipo deve essere uguale al tipo della proprietà. Il parametro rappresenta il valore della proprietà impostato. Se il parametro viene omesso, un parametro denominato Value viene dichiarato in modo implicito.

Nota. Set le funzioni di accesso hanno la stessa restrizione sulla posizione della riga a cui dispongono le subroutine. L'istruzione iniziale, l'istruzione end e il blocco devono essere tutti visualizzati all'inizio di una riga logica.

PropertySetDeclaration
    : Attributes? AccessModifier? 'Set'
      ( OpenParenthesis ParameterList? CloseParenthesis )? LineTerminator
      Block?
      'End' 'Set' StatementTerminator
    ;

Proprietà predefinite

Una proprietà che specifica il modificatore Default è denominata proprietà predefinita. Qualsiasi tipo che consente le proprietà può avere una proprietà predefinita, incluse le interfacce. È possibile fare riferimento alla proprietà predefinita senza dover qualificare l'istanza con il nome della proprietà. Pertanto, data una classe

Class Test
    Public Default ReadOnly Property Item(i As Integer) As Integer
        Get
            Return i
        End Get
    End Property
End Class

il codice

Module TestModule
    Sub Main()
        Dim x As Test = New Test()
        Dim y As Integer

        y = x(10)
    End Sub
End Module

equivale a

Module TestModule
    Sub Main()
        Dim x As Test = New Test()
        Dim y As Integer

        y = x.Item(10)
    End Sub
End Module

Una volta dichiarata Defaultuna proprietà , tutte le proprietà di cui è stato eseguito l'overload in tale nome nella gerarchia di ereditarietà diventano la proprietà predefinita, indipendentemente dal fatto che siano state dichiarate Default o meno. La dichiarazione di una proprietà Default in una classe derivata quando la classe base ha dichiarato una proprietà predefinita da un altro nome non richiede altri modificatori, ad Shadows esempio o Overrides. Ciò è dovuto al fatto che la proprietà predefinita non ha identità o firma e pertanto non può essere ombreggiata o sovraccaricata. Per esempio:

Class Base
    Public ReadOnly Default Property Item(i As Integer) As Integer
        Get
            Console.WriteLine("Base = " & i)
        End Get
    End Property
End Class

Class Derived
    Inherits Base

    ' This hides Item, but does not change the default property.
    Public Shadows ReadOnly Property Item(i As Integer) As Integer
        Get
            Console.WriteLine("Derived = " & i)
        End Get
    End Property
End Class

Class MoreDerived
    Inherits Derived

    ' This declares a new default property, but not Item.
    ' This does not need to be declared Shadows
    Public ReadOnly Default Property Value(i As Integer) As Integer
        Get
            Console.WriteLine("MoreDerived = " & i)
        End Get
    End Property
End Class

Module Test
    Sub Main()
        Dim x As MoreDerived = New MoreDerived()
        Dim y As Integer
        Dim z As Derived = x

        y = x(10)        ' Calls MoreDerived.Value.
        y = x.Item(10)   ' Calls Derived.Item
        y = z(10)        ' Calls Base.Item
    End Sub
End Module

Questo programma produrrà l'output:

MoreDerived = 10
Derived = 10
Base = 10

Tutte le proprietà predefinite dichiarate all'interno di un tipo devono avere lo stesso nome e, per maggiore chiarezza, deve specificare il Default modificatore. Poiché una proprietà predefinita senza parametri di indice causerebbe una situazione ambigua quando si assegnano istanze della classe contenitore, le proprietà predefinite devono avere parametri di indice. Inoltre, se una proprietà di cui è stato eseguito l'overload su un nome specifico include il modificatore, tutte le proprietà di cui è stato eseguito l'overload Default su tale nome devono specificarlo. Le proprietà predefinite potrebbero non essere Sharede almeno una funzione di accesso della proprietà non deve essere Private.

Proprietà implementate automaticamente

Se una proprietà omette una dichiarazione di funzioni di accesso, verrà fornita automaticamente un'implementazione della proprietà, a meno che la proprietà non venga dichiarata in un'interfaccia o sia dichiarata MustOverride. Solo le proprietà di lettura/scrittura senza argomenti possono essere implementate automaticamente; in caso contrario, si verifica un errore in fase di compilazione.

Una proprietà ximplementata automaticamente, anche una che esegue l'override di un'altra proprietà, introduce una variabile _x locale privata con lo stesso tipo della proprietà. Se si verifica un conflitto tra il nome della variabile locale e un'altra dichiarazione, verrà segnalato un errore in fase di compilazione. La funzione di accesso della Get proprietà implementata automaticamente restituisce il valore della funzione di Set accesso locale e della proprietà che imposta il valore della proprietà locale. Ad esempio, la dichiarazione:

Public Property x() As Integer

è approssimativamente equivalente a:

Private _x As Integer
Public Property x() As Integer
    Get
        Return _x
    End Get
    Set (value As Integer)
        _x = value
    End Set
End Property

Come per le dichiarazioni di variabili, una proprietà implementata automaticamente può includere un inizializzatore. Per esempio:

Public Property x() As Integer = 10
Public Shared Property y() As New Customer() With { .Name = "Bob" }

Nota. Quando viene inizializzata una proprietà implementata automaticamente, viene inizializzata tramite la proprietà , non il campo sottostante. In questo modo, l'override delle proprietà può intercettare l'inizializzazione se necessario.

Gli inizializzatori di matrice sono consentiti per le proprietà implementate automaticamente, ad eccezione del fatto che non è possibile specificare in modo esplicito i limiti della matrice. Per esempio:

' Valid
Property x As Integer() = {1, 2, 3}
Property y As Integer(,) = {{1, 2, 3}, {12, 13, 14}, {11, 10, 9}}

' Invalid
Property x4(5) As Short

Proprietà iteratore

Una proprietà iteratore è una proprietà con il Iterator modificatore. Viene usato per lo stesso motivo per cui viene usato un metodo iteratore ( metodi iteratore sezione), come modo pratico per generare una sequenza, che può essere utilizzata dall'istruzione For Each . La Get funzione di accesso di una proprietà iteratore viene interpretata nello stesso modo di un metodo iteratore.

Una proprietà iteratore deve avere una funzione di accesso esplicita Get e il relativo tipo deve essere IEnumerator, o IEnumerableo o IEnumerable(Of T)IEnumerator(Of T) per alcuni T.

Di seguito è riportato un esempio di proprietà iteratore:

Class Family
    Property Daughters As New List(Of String) From {"Beth", "Diane"}
    Property Sons As New List(Of String) From {"Abe", "Carl"}

    ReadOnly Iterator Property Children As IEnumerable(Of String)
        Get
            For Each name In Daughters : Yield name : Next
            For Each name In Sons : Yield name : Next
        End Get
    End Property
End Class

Module Module1
    Sub Main()
        Dim x As New Family
        For Each c In x.Children
            Console.WriteLine(c) ' prints Beth, Diane, Abe, Carl
        Next
    End Sub
End Module

Operatori

Gli operatori sono metodi che definiscono il significato di un operatore Visual Basic esistente per la classe contenitore. Quando l'operatore viene applicato alla classe in un'espressione, l'operatore viene compilato in una chiamata al metodo dell'operatore definito nella classe . La definizione di un operatore per una classe è nota anche come overload dell'operatore.

OperatorDeclaration
    : Attributes? OperatorModifier* 'Operator' OverloadableOperator
      OpenParenthesis ParameterList CloseParenthesis
      ( 'As' Attributes? TypeName )? LineTerminator
      Block?
      'End' 'Operator' StatementTerminator
    ;

OperatorModifier
    : 'Public' | 'Shared' | 'Overloads' | 'Shadows' | 'Widening' | 'Narrowing'
    ;

OverloadableOperator
    : '+' | '-' | '*' | '/' | '\\' | '&' | 'Like' | 'Mod' | 'And' | 'Or' | 'Xor'
    | '^' | '<' '<' | '>' '>' | '=' | '<' '>' | '>' | '<' | '>' '=' | '<' '='
    | 'Not' | 'IsTrue' | 'IsFalse' | 'CType'
    ;

Non è possibile eseguire l'overload di un operatore già esistente; in pratica, questo vale principalmente per gli operatori di conversione. Ad esempio, non è possibile eseguire l'overload della conversione da una classe derivata a una classe base:

Class Base
End Class

Class Derived
    ' Cannot redefine conversion from Derived to Base,
    ' conversion will be ignored.
    Public Shared Widening Operator CType(s As Derived) As Base
        ...
    End Operator
End Class

Gli operatori possono anche essere sovraccaricati nel senso comune della parola:

Class Base
    Public Shared Widening Operator CType(b As Base) As Integer
        ...
    End Operator

    Public Shared Narrowing Operator CType(i As Integer) As Base
        ...
    End Operator
End Class

Le dichiarazioni degli operatori non aggiungono in modo esplicito nomi allo spazio di dichiarazione del tipo contenitore; tuttavia dichiarano in modo implicito un metodo corrispondente a partire dai caratteri "op_". Le sezioni seguenti elencano i nomi dei metodi corrispondenti con ogni operatore.

Esistono tre classi di operatori che possono essere definiti: operatori unari, operatori binari e operatori di conversione. Tutte le dichiarazioni degli operatori condividono determinate restrizioni:

  • Le dichiarazioni degli operatori devono essere Public sempre e Shared. Il Public modificatore può essere omesso nei contesti in cui verrà assunto il modificatore.

  • I parametri di un operatore non possono essere dichiarati ByRefo OptionalParamArray.

  • Il tipo di almeno uno degli operandi o del valore restituito deve essere il tipo che contiene l'operatore .

  • Non esiste una variabile restituita dalla funzione definita per gli operatori. Pertanto, l'istruzione Return deve essere usata per restituire valori da un corpo di un operatore.

L'unica eccezione a queste restrizioni si applica ai tipi valore nullable. Poiché i tipi valore nullable non hanno una definizione di tipo effettiva, un tipo valore può dichiarare operatori definiti dall'utente per la versione nullable del tipo. Quando si determina se un tipo può dichiarare un particolare operatore definito dall'utente, i ? modificatori vengono prima eliminati da tutti i tipi coinvolti nella dichiarazione ai fini del controllo della validità. Questo relax non si applica al tipo restituito degli IsTrue operatori e IsFalse , ma deve comunque restituire Boolean, non Boolean?.

La precedenza e l'associatività di un operatore non possono essere modificate da una dichiarazione di operatore.

Nota. Gli operatori hanno la stessa restrizione sulla posizione della riga a cui dispongono le subroutine. L'istruzione iniziale, l'istruzione end e il blocco devono essere tutti visualizzati all'inizio di una riga logica.

Operatori unari

È possibile eseguire l'overload degli operatori unari seguenti:

  • Operatore + più unario (metodo corrispondente: op_UnaryPlus)

  • Operatore - meno unario (metodo corrispondente: op_UnaryNegation)

  • Operatore logico Not (metodo corrispondente: op_OnesComplement)

  • Operatori IsTrue e IsFalse (metodi corrispondenti: op_True, op_False)

Tutti gli operatori unari di overload devono accettare un singolo parametro del tipo contenitore e possono restituire qualsiasi tipo, ad eccezione IsTrue di e IsFalse, che deve restituire Boolean. Se il tipo contenitore è un tipo generico, i parametri di tipo devono corrispondere ai parametri di tipo del tipo contenitore. Ad esempio:

Structure Complex
    ...

    Public Shared Operator +(v As Complex) As Complex
        Return v
    End Operator
End Structure

Se un tipo esegue l'overload di uno di IsTrue o IsFalse, deve eseguire anche l'overload dell'altro. Se ne viene eseguito un solo overload, viene restituito un errore in fase di compilazione.

Nota. IsTrue e IsFalse non sono parole riservate.

Operatori binari

È possibile eseguire l'overload degli operatori binari seguenti:

  • Operatori di addizione +-, sottrazione , moltiplicazione *, divisione /integrale , modulo Mod\ed esponente ^ (metodo corrispondente: op_Addition, op_Divisionop_Multiplyop_IntegerDivisionop_Subtraction, op_Modulus, ) op_Exponent

  • Operatori relazionali =, <>, <>, , <=, ( >= metodi corrispondenti: op_Equality, op_Inequality). op_LessThanop_GreaterThanOrEqualop_GreaterThanop_LessThanOrEqual Nota. Anche se l'operatore di uguaglianza può essere sottoposto a overload, l'operatore di assegnazione (usato solo nelle istruzioni di assegnazione) non può essere sottoposto a overload.

  • Like Operatore (metodo corrispondente: op_Like)

  • Operatore & di concatenazione (metodo corrispondente: op_Concatenate)

  • Operatori logici Ande XorOr (metodi corrispondenti: op_BitwiseAnd, op_BitwiseOr, op_ExclusiveOr)

  • Operatori di spostamento << e >> (metodi corrispondenti: op_LeftShift, op_RightShift)

Tutti gli operatori binari di overload devono accettare il tipo contenitore come uno dei parametri. Se il tipo contenitore è un tipo generico, i parametri di tipo devono corrispondere ai parametri di tipo del tipo contenitore. Gli operatori di spostamento limitano ulteriormente questa regola per richiedere che il primo parametro sia del tipo contenitore; Il secondo parametro deve essere sempre di tipo Integer.

Gli operatori binari seguenti devono essere dichiarati in coppie:

  • Operatore = e operatore <>

  • Operatore > e operatore <

  • Operatore >= e operatore <=

Se una della coppia viene dichiarata, l'altra deve anche essere dichiarata con il parametro corrispondente e i tipi restituiti oppure verrà generato un errore in fase di compilazione. (Nota. Lo scopo di richiedere dichiarazioni abbinate di operatori relazionali consiste nel provare a garantire almeno un livello minimo di coerenza logica negli operatori di overload.

A differenza degli operatori relazionali, l'overload degli operatori di divisione e di divisione integrale è fortemente sconsigliato, anche se non un errore. (Nota. In generale, i due tipi di divisione devono essere completamente distinti: un tipo che supporta la divisione è integrale (nel qual caso deve supportare \) o meno (nel qual caso deve supportare /). È stato considerato un errore definire entrambi gli operatori, ma poiché i loro linguaggi in genere non distinguono tra due tipi di divisione il modo in cui Visual Basic fa, si ritiene che fosse più sicuro consentire la pratica, ma fortemente sconsigliato.

Gli operatori di assegnazione composti non possono essere sovraccaricati direttamente. Al contrario, quando viene eseguito l'overload dell'operatore binario corrispondente, l'operatore di assegnazione composta userà l'operatore di overload. Per esempio:

Structure Complex
    ...

    Public Shared Operator +(x As Complex, y As Complex) _
        As Complex
        ...
    End Operator
End Structure

Module Test
    Sub Main()
        Dim c1, c2 As Complex
        ' Calls the overloaded + operator
        c1 += c2
    End Sub
End Module

Operatori di conversione

Gli operatori di conversione definiscono nuove conversioni tra tipi. Queste nuove conversioni sono denominate conversioni definite dall'utente. Un operatore di conversione converte da un tipo di origine, indicato dal tipo di parametro dell'operatore di conversione, a un tipo di destinazione, indicato dal tipo restituito dell'operatore di conversione. Le conversioni devono essere classificate come più grandi o più strette. Una dichiarazione dell'operatore di conversione che include la Widening parola chiave introduce una conversione con estensione definita dall'utente (metodo corrispondente: op_Implicit). Una dichiarazione dell'operatore di conversione che include la Narrowing parola chiave introduce una conversione di narrowing definita dall'utente (metodo corrispondente: op_Explicit).

In generale, le conversioni di estensione definite dall'utente devono essere progettate per non generare mai eccezioni e non perdere mai informazioni. Se una conversione definita dall'utente può causare eccezioni (ad esempio, perché l'argomento di origine non è compreso nell'intervallo) o la perdita di informazioni (ad esempio l'eliminazione di bit di ordine elevato), tale conversione deve essere definita come conversione verso un tipo di dati più piccolo. Nell'esempio:

Structure Digit
    Dim value As Byte

    Public Sub New(value As Byte)
        if value < 0 OrElse value > 9 Then Throw New ArgumentException()
        Me.value = value
    End Sub

    Public Shared Widening Operator CType(d As Digit) As Byte
        Return d.value
    End Operator

    Public Shared Narrowing Operator CType(b As Byte) As Digit
        Return New Digit(b)
    End Operator
End Structure

la conversione da Digit a Byte è una conversione verso un tipo di dati più ampio perché non genera mai eccezioni o perde informazioni, ma la conversione da Byte a Digit è una conversione Digit verso un tipo di dati più piccolo perché può rappresentare solo un subset dei valori possibili di un oggetto Byte.

A differenza di tutti gli altri membri di tipo che possono essere sovraccaricati, la firma di un operatore di conversione include il tipo di destinazione della conversione. Si tratta dell'unico membro di tipo per il quale il tipo restituito partecipa alla firma. La classificazione verso un operatore di conversione più ampia o ridotta non fa tuttavia parte della firma dell'operatore. Pertanto, una classe o una struttura non può dichiarare sia un operatore di conversione di tipo widening che un operatore di conversione verso un tipo di dati di tipo narrowing con gli stessi tipi di origine e di destinazione.

Un operatore di conversione definito dall'utente deve convertire in o dal tipo contenitore, ad esempio è possibile che una classe C definisci una conversione da C e Integer verso , ma non da IntegerInteger a CBoolean. Se il tipo contenitore è un tipo generico, i parametri di tipo devono corrispondere ai parametri di tipo del tipo contenitore. Inoltre, non è possibile ridefinire una conversione intrinseca (ovvero non definita dall'utente). Di conseguenza, un tipo non può dichiarare una conversione in cui:

  • Il tipo di origine e il tipo di destinazione sono uguali.

  • Sia il tipo di origine che il tipo di destinazione non sono il tipo che definisce l'operatore di conversione.

  • Il tipo di origine o il tipo di destinazione è un tipo di interfaccia.

  • I tipi di origine e di destinazione sono correlati dall'ereditarietà (incluso Object).

L'unica eccezione a queste regole si applica ai tipi valore nullable. Poiché i tipi valore nullable non hanno una definizione di tipo effettiva, un tipo valore può dichiarare conversioni definite dall'utente per la versione nullable del tipo. Quando si determina se un tipo può dichiarare una particolare conversione definita dall'utente, i ? modificatori vengono prima eliminati da tutti i tipi coinvolti nella dichiarazione ai fini del controllo della validità. Di conseguenza, la dichiarazione seguente è valida perché S può definire una conversione da S a T:

Structure T
    ...
End Structure

Structure S
    Public Shared Widening Operator CType(ByVal v As S?) As T
    ...
    End Operator
End Structure

La dichiarazione seguente non è tuttavia valida, perché la struttura S non può definire una conversione da S a S:

Structure S
    Public Shared Widening Operator CType(ByVal v As S) As S?
        ...
    End Operator
End Structure

Mapping degli operatori

Poiché il set di operatori supportati da Visual Basic potrebbe non corrispondere esattamente al set di operatori di altri linguaggi in .NET Framework, alcuni operatori vengono mappati appositamente ad altri operatori quando vengono definiti o usati. In particolare:

  • La definizione di un operatore di divisione integrale definirà automaticamente un operatore di divisione regolare (utilizzabile solo da altri linguaggi) che chiamerà l'operatore di divisione integrale.

  • L'overload degli Notoperatori , Ande Or eseguirà l'overload solo dell'operatore bit per bit dal punto di vista di altri linguaggi che distinguono gli operatori logici e bit per bit.

  • Una classe che esegue l'overload solo degli operatori logici in un linguaggio che distingue tra operatori logici e bit per bit (ad esempio, un linguaggio che usa op_LogicalNotrispettivamente , op_LogicalAnde op_LogicalOr per NotAnd, e Or) avrà i relativi operatori logici mappati agli operatori logici di Visual Basic. Se entrambi gli operatori logici e bit per bit sono in overload, verranno usati solo gli operatori bit per bit.

  • L'overload degli operatori e esegue >> l'overload << solo degli operatori firmati dal punto di vista di altri linguaggi che distinguono gli operatori di spostamento con segno e senza segno.

  • Una classe che esegue l'overload solo di un operatore di spostamento senza segno avrà l'operatore shift senza segno mappato all'operatore di spostamento visual Basic corrispondente. Se viene eseguito l'overload di un operatore di spostamento senza segno e con segno, verrà usato solo l'operatore shift con segno.