Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questo capitolo illustra alcuni concetti necessari per comprendere la semantica del linguaggio Microsoft Visual Basic. Molti dei concetti devono essere familiari con i programmatori di Visual Basic o I programmatori C/C++, ma le definizioni precise possono differire.
Dichiarazioni
Un programma Visual Basic è costituito da entità denominate. Queste entità vengono introdotte tramite dichiarazioni e rappresentano il "significato" del programma.
A livello superiore, gli spazi dei nomi sono entità che organizzano altre entità, ad esempio spazi dei nomi e tipi annidati. I tipi sono entità che descrivono i valori e definiscono il codice eseguibile. I tipi possono contenere tipi annidati e membri di tipo. I membri del tipo sono costanti, variabili, metodi, operatori, proprietà, eventi, valori di enumerazione e costruttori.
Un'entità che può contenere altre entità definisce uno spazio di dichiarazione. Le entità vengono introdotte in uno spazio di dichiarazione tramite dichiarazioni o ereditarietà; lo spazio di dichiarazione contenitore è denominato contesto di dichiarazione delle entità. La dichiarazione di un'entità in uno spazio di dichiarazione definisce a sua volta un nuovo spazio di dichiarazione che può contenere altre dichiarazioni di entità annidate; pertanto, le dichiarazioni in un programma formano una gerarchia di spazi di dichiarazione.
Tranne nel caso di membri di tipo di overload, non è valido per le dichiarazioni introdurre entità con nome identico dello stesso tipo nello stesso contesto di dichiarazione. Inoltre, uno spazio di dichiarazione potrebbe non contenere mai tipi diversi di entità con lo stesso nome; Ad esempio, uno spazio di dichiarazione potrebbe non contenere mai una variabile e un metodo con lo stesso nome.
Nota. In altri linguaggi può essere possibile creare uno spazio di dichiarazione contenente diversi tipi di entità con lo stesso nome, ad esempio se la lingua fa distinzione tra maiuscole e minuscole e consente dichiarazioni diverse in base alle maiuscole e minuscole. In tale situazione, l'entità più accessibile viene considerata associata a tale nome; se più di un tipo di entità è più accessibile, il nome è ambiguo.
Public è più accessibile di Protected Friend, Protected Friend è più accessibile di Protected o Friende Protected o Friend è più accessibile di Private.
Lo spazio di dichiarazione di uno spazio dei nomi è "aperto", quindi due dichiarazioni dello spazio dei nomi con lo stesso nome completo contribuiscono allo stesso spazio di dichiarazione. Nell'esempio seguente le due dichiarazioni dello spazio dei nomi contribuiscono allo stesso spazio di dichiarazione, in questo caso dichiarando due classi con i nomi Data.Customer completi e Data.Order:
Namespace Data
Class Customer
End Class
End Namespace
Namespace Data
Class Order
End Class
End Namespace
Poiché le due dichiarazioni contribuiscono allo stesso spazio di dichiarazione, si verificherà un errore in fase di compilazione se ognuna conteneva una dichiarazione di una classe con lo stesso nome.
Overload e firme
L'unico modo per dichiarare entità denominate identiche dello stesso tipo in uno spazio di dichiarazione consiste nell'eseguire l'overload. È possibile eseguire l'overload solo di metodi, operatori, costruttori di istanza e proprietà.
I membri del tipo di overload devono possedere firme univoche. La firma di un membro di tipo è costituita dal numero di parametri di tipo e dal numero e dai tipi dei parametri del membro. Gli operatori di conversione includono anche il tipo restituito dell'operatore nella firma.
Di seguito non si fa parte della firma di un membro e pertanto non è possibile eseguire l'overload su:
Modificatori a un membro di tipo (ad esempio,
SharedoPrivate).Modificatori di un parametro (ad esempio,
ByValoByRef).Nomi dei parametri.
Tipo restituito di un metodo o di un operatore (ad eccezione degli operatori di conversione) o del tipo di elemento di una proprietà.
Vincoli per un parametro di tipo.
Nell'esempio seguente viene illustrato un set di dichiarazioni di metodo di overload insieme alle relative firme. Questa dichiarazione non sarebbe valida perché diverse dichiarazioni di metodo hanno firme identiche.
Interface ITest
Sub F1() ' Signature is ().
Sub F2(x As Integer) ' Signature is (Integer).
Sub F3(ByRef x As Integer) ' Signature is (Integer).
Sub F4(x As Integer, y As Integer) ' Signature is (Integer, Integer).
Function F5(s As String) As Integer ' Signature is (String).
Function F6(x As Integer) As Integer ' Signature is (Integer).
Sub F7(a() As String) ' Signature is (String()).
Sub F8(ParamArray a() As String) ' Signature is (String()).
Sub F9(Of T)() ' Signature is !1().
Sub F10(Of T, U)(x As T, y As U) ' Signature is !2(!1, !2)
Sub F11(Of U, T)(x As T, y As U) ' Signature is !2(!2, !1)
Sub F12(Of T)(x As T) ' Signature is !1(!1)
Sub F13(Of T As IDisposable)(x As T) ' Signature is !1(!1)
End Interface
È valido definire un tipo generico che può contenere membri con firme identiche in base agli argomenti di tipo forniti. Le regole di risoluzione dell'overload vengono usate per provare e disambiguare tra tali overload, anche se possono verificarsi situazioni in cui è impossibile disambiguare. Per esempio:
Class C(Of T)
Sub F(x As Integer)
End Sub
Sub F(x As T)
End Sub
Sub G(Of U)(x As T, y As U)
End Sub
Sub G(Of U)(x As U, y As T)
End Sub
End Class
Module Test
Sub Main()
Dim x As New C(Of Integer)
x.F(10) ' Calls C(Of T).F(Integer)
x.G(Of Integer)(10, 10) ' Error: Can't choose between overloads
End Sub
End Module
Ambito
L'ambito del nome di un'entità è il set di tutti gli spazi di dichiarazione in cui è possibile fare riferimento a tale nome senza qualifica. In generale, l'ambito del nome di un'entità è l'intero contesto di dichiarazione; Tuttavia, la dichiarazione di un'entità può contenere dichiarazioni annidate di entità con lo stesso nome. In tal caso, l'entità annidata nasconde o nasconde l'entità esterna e l'accesso all'entità ombreggiata è possibile solo tramite la qualificazione.
L'ombreggiatura tramite annidamento si verifica negli spazi dei nomi o nei tipi annidati all'interno degli spazi dei nomi, nei tipi annidati all'interno di altri tipi e nei corpi dei membri di tipo. L'ombreggiatura attraverso l'annidamento delle dichiarazioni avviene sempre in modo implicito; non è necessaria alcuna sintassi esplicita.
Nell'esempio seguente, all'interno del F metodo , la variabile i di istanza è ombreggiata dalla variabile ilocale , ma all'interno del G metodo , i fa comunque riferimento alla variabile di istanza.
Class Test
Private i As Integer = 0
Sub F()
Dim i As Integer = 1
End Sub
Sub G()
i = 1
End Sub
End Class
Quando un nome in un ambito interno nasconde un nome in un ambito esterno, nasconde tutte le occorrenze di overload di tale nome. Nell'esempio seguente la chiamata F(1) richiama l'oggetto F dichiarato in Inner perché tutte le occorrenze esterne di F sono nascoste dalla dichiarazione interna. Per lo stesso motivo, la chiamata F("Hello") è in errore.
Class Outer
Shared Sub F(i As Integer)
End Sub
Shared Sub F(s As String)
End Sub
Class Inner
Shared Sub F(l As Long)
End Sub
Sub G()
F(1) ' Invokes Outer.Inner.F.
F("Hello") ' Error.
End Sub
End Class
End Class
Ereditarietà
Una relazione di ereditarietà è una in cui un tipo (tipo derivato ) deriva da un altro (tipo di base ), in modo che lo spazio di dichiarazione del tipo derivato contenga in modo implicito i membri del tipo non costruttore accessibili e i tipi annidati del relativo tipo di base. Nell'esempio seguente la classe A è la classe base di Bed B è derivata da A.
Class A
End Class
Class B
Inherits A
End Class
Poiché A non specifica in modo esplicito una classe base, la relativa classe di base è implicitamente Object.
Di seguito sono riportati alcuni aspetti importanti dell'ereditarietà:
L'ereditarietà è transitiva. Se il tipo C è derivato dal tipo B e il tipo B è derivato dal tipo A, il tipo C eredita i membri del tipo dichiarati nel tipo B e i membri del tipo dichiarati nel tipo A.
Un tipo derivato si estende, ma non può restringere il tipo di base. Un tipo derivato può aggiungere nuovi membri di tipo e può nascondere i membri dei tipi ereditati, ma non può rimuovere la definizione di un membro del tipo ereditato.
Poiché un'istanza di un tipo contiene tutti i membri del tipo di base, esiste sempre una conversione da un tipo derivato al relativo tipo di base.
Tutti i tipi devono avere un tipo di base, ad eccezione del tipo
Object. Pertanto,Objectè il tipo di base finale di tutti i tipi e tutti i tipi possono essere convertiti in esso.La circolarità nella derivazione non è consentita. Ovvero, quando un tipo
Bderiva da un tipoA, si tratta di un errore per il tipoAper derivare direttamente o indirettamente dal tipoB.Un tipo non può derivare direttamente o indirettamente da un tipo annidato al suo interno.
L'esempio seguente genera un errore in fase di compilazione perché le classi dipendono in modo circolare l'una dall'altra.
Class A
Inherits B
End Class
Class B
Inherits C
End Class
Class C
Inherits A
End Class
L'esempio seguente genera anche un errore in fase di compilazione perché B deriva indirettamente dalla classe C nidificata tramite la classe A.
Class A
Inherits B.C
End Class
Class B
Inherits A
Public Class C
End Class
End Class
L'esempio successivo non genera un errore perché la classe A non deriva dalla classe B.
Class A
Class B
Inherits A
End Class
End Class
Classi MustInherit e NotInheritable
Una MustInherit classe è un tipo incompleto che può fungere solo da tipo di base. Non è possibile creare un'istanza di una MustInherit classe, pertanto è un errore usare l'operatore New su uno. È valido dichiarare variabili di MustInherit classi. Tali variabili possono essere assegnate Nothing solo o un valore di una classe derivata dalla MustInherit classe .
Quando una classe regolare è derivata da una MustInherit classe , la classe regolare deve eseguire l'override di tutti i membri ereditati MustOverride . Per esempio:
MustInherit Class A
Public MustOverride Sub F()
End Class
MustInherit Class B
Inherits A
Public Sub G()
End Sub
End Class
Class C
Inherits B
Public Overrides Sub F()
End Sub
End Class
La MustInherit classe A introduce un MustOverride metodo F. La classe B introduce un metodo Gaggiuntivo , ma non fornisce un'implementazione di F. La classe B deve quindi essere dichiarata MustInheritanche . La classe C esegue l'override F e fornisce un'implementazione effettiva. Poiché non sono presenti membri in sospeso MustOverride nella classe C, non è necessario essere MustInherit.
Una NotInheritable classe è una classe da cui non è possibile derivare un'altra classe.
NotInheritable le classi vengono usate principalmente per impedire la derivazione imprevista.
In questo esempio la classe B è in errore perché tenta di derivare dalla NotInheritable classe A. Non è possibile contrassegnare una classe sia MustInherit che NotInheritable.
NotInheritable Class A
End Class
Class B
' Error, a class cannot derive from a NotInheritable class.
Inherits A
End Class
Interfacce e ereditarietà multipla
A differenza di altri tipi, che derivano solo da un singolo tipo di base, un'interfaccia può derivare da più interfacce di base. Per questo motivo, un'interfaccia può ereditare un membro di tipo con nome identico da interfacce di base diverse. In questo caso, il nome ereditato dalla moltiplicazione non è disponibile nell'interfaccia derivata e il riferimento a uno di questi membri di tipo tramite l'interfaccia derivata causa un errore in fase di compilazione, indipendentemente dalle firme o dall'overload. È invece necessario fare riferimento ai membri di tipo in conflitto tramite un nome di interfaccia di base.
Nell'esempio seguente le prime due istruzioni causano errori in fase di compilazione perché il membro Count ereditato dalla moltiplicazione non è disponibile nell'interfaccia IListCounter:
Interface IList
Property Count() As Integer
End Interface
Interface ICounter
Sub Count(i As Integer)
End Interface
Interface IListCounter
Inherits IList
Inherits ICounter
End Interface
Module Test
Sub F(x As IListCounter)
x.Count(1) ' Error, Count is not available.
x.Count = 1 ' Error, Count is not available.
CType(x, IList).Count = 1 ' Ok, invokes IList.Count.
CType(x, ICounter).Count(1) ' Ok, invokes ICounter.Count.
End Sub
End Module
Come illustrato nell'esempio, l'ambiguità viene risolta eseguendo il cast x al tipo di interfaccia di base appropriato. Tali cast non hanno costi di run-time; sono semplicemente costituiti dalla visualizzazione dell'istanza come tipo meno derivato in fase di compilazione.
Quando un singolo membro di tipo viene ereditato dalla stessa interfaccia di base tramite più percorsi, il membro del tipo viene considerato come se fosse ereditato una sola volta. In altre parole, l'interfaccia derivata contiene solo un'istanza di ogni membro del tipo ereditato da una particolare interfaccia di base. Per esempio:
Interface IBase
Sub F(i As Integer)
End Interface
Interface ILeft
Inherits IBase
End Interface
Interface IRight
Inherits IBase
End Interface
Interface IDerived
Inherits ILeft, IRight
End Interface
Class Derived
Implements IDerived
' Only have to implement F once.
Sub F(i As Integer) Implements IDerived.F
End Sub
End Class
Se il nome di un membro di tipo è ombreggiato in un percorso attraverso la gerarchia di ereditarietà, il nome viene ombreggiato in tutti i percorsi. Nell'esempio seguente il IBase.F membro è ombreggiato dal ILeft.F membro, ma non è ombreggiato in IRight:
Interface IBase
Sub F(i As Integer)
End Interface
Interface ILeft
Inherits IBase
Shadows Sub F(i As Integer)
End Interface
Interface IRight
Inherits IBase
Sub G()
End Interface
Interface IDerived
Inherits ILeft, IRight
End Interface
Class Test
Sub H(d As IDerived)
d.F(1) ' Invokes ILeft.F.
CType(d, IBase).F(1) ' Invokes IBase.F.
CType(d, ILeft).F(1) ' Invokes ILeft.F.
CType(d, IRight).F(1) ' Invokes IBase.F.
End Sub
End Class
La chiamata d.F(1) seleziona ILeft.F, anche se IBase.F sembra non essere ombreggiata nel percorso di accesso che conduce attraverso IRight. Poiché il percorso di accesso da IDerived a ombreggiatura IBase.FIBase , il membro viene ombreggiato anche nel percorso di accesso da IDerived a a IBaseIRightILeft .
Ombreggiamento
Un tipo derivato ombreggiato il nome di un membro di tipo ereditato dichiarandolo nuovamente. L'ombreggiatura di un nome non rimuove i membri del tipo ereditati con tale nome; rende semplicemente non disponibili tutti i membri del tipo ereditato con tale nome nella classe derivata. La dichiarazione di shadowing può essere qualsiasi tipo di entità.
Le entità che possono essere sottoposte a overload possono scegliere una delle due forme di ombreggiatura.
L'ombreggiatura per nome viene specificata usando la Shadows parola chiave . Un'entità che ombreggiata per nome nasconde tutti gli elementi in base a tale nome nella classe di base, inclusi tutti gli overload.
L'ombreggiatura in base al nome e alla firma viene specificata usando la Overloads parola chiave . Un'entità che nasconde in base al nome e alla firma tutti gli elementi con tale nome con la stessa firma dell'entità. Per esempio:
Class Base
Sub F()
End Sub
Sub F(i As Integer)
End Sub
Sub G()
End Sub
Sub G(i As Integer)
End Sub
End Class
Class Derived
Inherits Base
' Only hides F(Integer).
Overloads Sub F(i As Integer)
End Sub
' Hides G() and G(Integer).
Shadows Sub G(i As Integer)
End Sub
End Class
Module Test
Sub Main()
Dim x As New Derived()
x.F() ' Calls Base.F().
x.G() ' Error: Missing parameter.
End Sub
End Module
L'ombreggiatura di un metodo con un ParamArray argomento per nome e firma nasconde solo la singola firma, non tutte le firme espanse possibili. Questo vale anche se la firma del metodo shadowing corrisponde alla firma non espansa del metodo ombreggiato. L'esempio seguente:
Class Base
Sub F(ParamArray x() As Integer)
Console.WriteLine("Base")
End Sub
End Class
Class Derived
Inherits Base
Overloads Sub F(x() As Integer)
Console.WriteLine("Derived")
End Sub
End Class
Module Test
Sub Main
Dim d As New Derived()
d.F(10)
End Sub
End Module
Basestampa , anche se Derived.F ha la stessa firma del formato non espanso di Base.F.
Viceversa, un metodo con un ParamArray argomento ombreggiate solo metodi con la stessa firma, non tutte le firme espanse possibili. L'esempio seguente:
Class Base
Sub F(x As Integer)
Console.WriteLine("Base")
End Sub
End Class
Class Derived
Inherits Base
Overloads Sub F(ParamArray x() As Integer)
Console.WriteLine("Derived")
End Sub
End Class
Module Test
Sub Main()
Dim d As New Derived()
d.F(10)
End Sub
End Module
Basestampa , anche se Derived.F ha un form espanso con la stessa firma di Base.F.
Metodo o proprietà shadowing che non specifica Shadows o Overloads presuppone Overloads se il metodo o la proprietà è dichiarato Overrides, Shadows in caso contrario. Se un membro di un set di entità di overload specifica la Shadows parola chiave o Overloads , è necessario specificarla. Le Shadows parole chiave e Overloads non possono essere specificate contemporaneamente. Né Shadows né Overloads possono essere specificati in un modulo standard; i membri di un modulo standard shadow in modo implicito vengono ereditati da Object.
È valido nascondere il nome di un membro di tipo che è stato ereditato tramite ereditarietà dell'interfaccia (e che in tal modo non è disponibile), rendendo il nome disponibile nell'interfaccia derivata.
Per esempio:
Interface ILeft
Sub F()
End Interface
Interface IRight
Sub F()
End Interface
Interface ILeftRight
Inherits ILeft, IRight
Shadows Sub F()
End Interface
Module Test
Sub G(i As ILeftRight)
i.F() ' Calls ILeftRight.F.
CType(i, ILeft).F() ' Calls ILeft.F.
CType(i, IRight).F() ' Calls IRight.F.
End Sub
End Module
Poiché i metodi possono nascondere i metodi ereditati, è possibile che una classe contenga diversi Overridable metodi con la stessa firma. Questo non presenta un problema di ambiguità, poiché è visibile solo il metodo più derivato. Nell'esempio seguente le C classi e D contengono due Overridable metodi con la stessa firma:
Class A
Public Overridable Sub F()
Console.WriteLine("A.F")
End Sub
End Class
Class B
Inherits A
Public Overrides Sub F()
Console.WriteLine("B.F")
End Sub
End Class
Class C
Inherits B
Public Shadows Overridable Sub F()
Console.WriteLine("C.F")
End Sub
End Class
Class D
Inherits C
Public Overrides Sub F()
Console.WriteLine("D.F")
End Sub
End Class
Module Test
Sub Main()
Dim d As New D()
Dim a As A = d
Dim b As B = d
Dim c As C = d
a.F()
b.F()
c.F()
d.F()
End Sub
End Module
In questo caso sono disponibili due Overridable metodi: uno introdotto dalla classe A e quello introdotto dalla classe C. Il metodo introdotto dalla classe C nasconde il metodo ereditato dalla classe A. Pertanto, la dichiarazione nella classe D esegue l'override Overrides del metodo introdotto dalla classe Ce non è possibile eseguire D l'override del metodo introdotto dalla classe A. L'esempio produce l'output:
B.F
B.F
D.F
D.F
È possibile richiamare il metodo nascosto Overridable accedendo a un'istanza della classe D tramite un tipo meno derivato in cui il metodo non è nascosto.
Non è valida l'ombreggiatura di un MustOverride metodo, perché nella maggior parte dei casi questa operazione renderebbe inutilizzabile la classe. Per esempio:
MustInherit Class Base
Public MustOverride Sub F()
End Class
MustInherit Class Derived
Inherits Base
Public Shadows Sub F()
End Sub
End Class
Class MoreDerived
Inherits Derived
' Error: MustOverride method Base.F is not overridden.
End Class
In questo caso, la classe MoreDerived è necessaria per eseguire l'override del MustOverride metodo Base.F, ma poiché la classe Derived shadows Base.F, questo non è possibile. Non è possibile dichiarare un discendente valido di Derived.
A differenza dell'ombreggiatura di un nome da un ambito esterno, l'ombreggiatura di un nome accessibile da un ambito ereditato causa la segnalazione di un avviso, come nell'esempio seguente:
Class Base
Public Sub F()
End Sub
Private Sub G()
End Sub
End Class
Class Derived
Inherits Base
Public Sub F() ' Warning: shadowing an inherited name.
End Sub
Public Sub G() ' No warning, Base.G is not accessible here.
End Sub
End Class
La dichiarazione del metodo F nella classe Derived fa sì che venga segnalato un avviso. L'ombreggiatura di un nome ereditato non è in particolare un errore, poiché ciò impedisce un'evoluzione separata delle classi di base. Ad esempio, la situazione precedente potrebbe verificarsi perché una versione successiva della classe Base ha introdotto un metodo F che non era presente in una versione precedente della classe. Se la situazione precedente è stata un errore, qualsiasi modifica apportata a una classe di base in una libreria di classi con controllo delle versioni separatamente potrebbe potenzialmente causare l'invalidità delle classi derivate.
L'avviso causato dall'ombreggiatura di un nome ereditato può essere eliminato tramite l'uso del Shadows modificatore o Overloads :
Class Base
Public Sub F()
End Sub
End Class
Class Derived
Inherits Base
Public Shadows Sub F() 'OK.
End Sub
End Class
Il Shadows modificatore indica l'intenzione di nascondere il membro ereditato. Non è un errore specificare il Shadows modificatore o Overloads se non è presente alcun nome membro di tipo da nascondere.
Una dichiarazione di un nuovo membro ombreggiate da un membro ereditato solo all'interno dell'ambito del nuovo membro, come nell'esempio seguente:
Class Base
Public Shared Sub F()
End Sub
End Class
Class Derived
Inherits Base
Private Shared Shadows Sub F() ' Shadows Base.F in class Derived only.
End Sub
End Class
Class MoreDerived
Inherits Derived
Shared Sub G()
F() ' Invokes Base.F.
End Sub
End Class
Nell'esempio precedente, la dichiarazione del metodo F nella classe Derived shadows il metodo F ereditato dalla classe Base, ma poiché il nuovo metodo F nella classe Derived ha Private accesso, il relativo ambito non si estende alla classe MoreDerived. Pertanto, la chiamata F() in MoreDerived.G è valida e richiamerà Base.F. Nel caso di membri di tipo di overload, l'intero set di membri del tipo di overload viene considerato come se avessero tutti l'accesso più permissivo ai fini dell'ombreggiatura.
Class Base
Public Sub F()
End Sub
End Class
Class Derived
Inherits Base
Private Shadows Sub F()
End Sub
Public Shadows Sub F(i As Integer)
End Sub
End Class
Class MoreDerived
Inherits Derived
Public Sub G()
F() ' Error. No accessible member with this signature.
End Sub
End Class
In questo esempio, anche se la dichiarazione di F() in Derived viene dichiarata con Private accesso, l'overload viene F(Integer) dichiarato con Public accesso. Pertanto, ai fini dell'ombreggiatura, il nome F in Derived viene considerato come se fosse Public, quindi entrambi i metodi ombreggiatura F in Base.
Implementazione
Esiste una relazione di implementazione quando un tipo dichiara che implementa un'interfaccia e il tipo implementa tutti i membri di tipo dell'interfaccia. Un tipo che implementa una particolare interfaccia è convertibile in tale interfaccia. Non è possibile creare un'istanza delle interfacce, ma è valida dichiarare variabili di interfacce; Tali variabili possono essere assegnate solo a un valore di una classe che implementa l'interfaccia . Per esempio:
Interface ITestable
Function Test(value As Byte) As Boolean
End Interface
Class TestableClass
Implements ITestable
Function Test(value As Byte) As Boolean Implements ITestable.Test
Return value > 128
End Function
End Class
Module Test
Sub F()
Dim x As ITestable = New TestableClass
Dim b As Boolean
b = x.Test(34)
End Sub
End Module
Un tipo che implementa un'interfaccia con membri di tipo ereditati multipli deve comunque implementare tali metodi, anche se non è possibile accedervi direttamente dall'interfaccia derivata implementata. Per esempio:
Interface ILeft
Sub Test()
End Interface
Interface IRight
Sub Test()
End Interface
Interface ILeftRight
Inherits ILeft, IRight
End Interface
Class LeftRight
Implements ILeftRight
' Has to reference ILeft explicitly.
Sub TestLeft() Implements ILeft.Test
End Sub
' Has to reference IRight explicitly.
Sub TestRight() Implements IRight.Test
End Sub
' Error: Test is not available in ILeftRight.
Sub TestLeftRight() Implements ILeftRight.Test
End Sub
End Class
Anche MustInherit le classi devono fornire implementazioni di tutti i membri delle interfacce implementate; tuttavia, possono rinviare l'implementazione di questi metodi dichiarandoli come MustOverride. Per esempio:
Interface ITest
Sub Test1()
Sub Test2()
End Interface
MustInherit Class TestBase
Implements ITest
' Provides an implementation.
Sub Test1() Implements ITest.Test1
End Sub
' Defers implementation.
MustOverride Sub Test2() Implements ITest.Test2
End Class
Class TestDerived
Inherits TestBase
' Have to implement MustOverride method.
Overrides Sub Test2()
End Sub
End Class
Un tipo può scegliere di implementare nuovamente un'interfaccia implementata dal tipo di base. Per implementare nuovamente l'interfaccia, il tipo deve dichiarare in modo esplicito che implementa l'interfaccia . Un tipo che implementa nuovamente un'interfaccia può scegliere di implementare di nuovo solo alcuni membri, ma non tutti, dei membri dell'interfaccia. Tutti i membri non implementati di nuovo continuano a usare l'implementazione del tipo di base. Per esempio:
Class TestBase
Implements ITest
Sub Test1() Implements ITest.Test1
Console.WriteLine("TestBase.Test1")
End Sub
Sub Test2() Implements ITest.Test2
Console.WriteLine("TestBase.Test2")
End Sub
End Class
Class TestDerived
Inherits TestBase
Implements ITest ' Required to re-implement
Sub DerivedTest1() Implements ITest.Test1
Console.WriteLine("TestDerived.DerivedTest1")
End Sub
End Class
Module Test
Sub Main()
Dim Test As ITest = New TestDerived()
Test.Test1()
Test.Test2()
End Sub
End Module
Questo esempio stampa:
TestDerived.DerivedTest1
TestBase.Test2
Quando un tipo derivato implementa un'interfaccia le cui interfacce di base vengono implementate dai tipi di base del tipo derivato, il tipo derivato può scegliere di implementare solo i membri di tipo dell'interfaccia non già implementati dai tipi di base. Per esempio:
Interface IBase
Sub Base()
End Interface
Interface IDerived
Inherits IBase
Sub Derived()
End Interface
Class Base
Implements IBase
Public Sub Base() Implements IBase.Base
End Sub
End Class
Class Derived
Inherits Base
Implements IDerived
' Required: IDerived.Derived not implemented by Base.
Public Sub Derived() Implements IDerived.Derived
End Sub
End Class
È anche possibile implementare un metodo di interfaccia usando un metodo sostituibile in un tipo di base. In tal caso, un tipo derivato può anche eseguire l'override del metodo sostituibile e modificare l'implementazione dell'interfaccia. Per esempio:
Class Base
Implements ITest
Public Sub Test1() Implements ITest.Test1
Console.WriteLine("TestBase.Test1")
End Sub
Public Overridable Sub Test2() Implements ITest.Test2
Console.WriteLine("TestBase.Test2")
End Sub
End Class
Class Derived
Inherits Base
' Overrides base implementation.
Public Overrides Sub Test2()
Console.WriteLine("TestDerived.Test2")
End Sub
End Class
Implementazione di metodi
Un tipo implementa un membro di tipo di un'interfaccia implementata fornendo un metodo con una Implements clausola . I due membri del tipo devono avere lo stesso numero di parametri, tutti i tipi e i modificatori dei parametri devono corrispondere, incluso il valore predefinito dei parametri facoltativi, il tipo restituito deve corrispondere e tutti i vincoli sui parametri del metodo devono corrispondere. Per esempio:
Interface ITest
Sub F(ByRef x As Integer)
Sub G(Optional y As Integer = 20)
Sub H(Paramarray z() As Integer)
End Interface
Class Test
Implements ITest
' Error: ByRef/ByVal mismatch.
Sub F(x As Integer) Implements ITest.F
End Sub
' Error: Defaults do not match.
Sub G(Optional y As Integer = 10) Implements ITest.G
End Sub
' Error: Paramarray does not match.
Sub H(z() As Integer) Implements ITest.H
End Sub
End Class
Un singolo metodo può implementare un numero qualsiasi di membri del tipo di interfaccia se soddisfano tutti i criteri precedenti. Per esempio:
Interface ITest
Sub F(i As Integer)
Sub G(i As Integer)
End Interface
Class Test
Implements ITest
Sub F(i As Integer) Implements ITest.F, ITest.G
End Sub
End Class
Quando si implementa un metodo in un'interfaccia generica, il metodo di implementazione deve fornire gli argomenti di tipo che corrispondono ai parametri di tipo dell'interfaccia. Per esempio:
Interface I1(Of U, V)
Sub M(x As U, y As List(Of V))
End Interface
Class C1(Of W, X)
Implements I1(Of W, X)
' W corresponds to U and X corresponds to V
Public Sub M(x As W, y As List(Of X)) Implements I1(Of W, X).M
End Sub
End Class
Class C2
Implements I1(Of String, Integer)
' String corresponds to U and Integer corresponds to V
Public Sub M(x As String, y As List(Of Integer)) _
Implements I1(Of String, Integer).M
End Sub
End Class
Si noti che è possibile che un'interfaccia generica non sia implementabile per alcuni set di argomenti di tipo.
Interface I1(Of T, U)
Sub S1(x As T)
Sub S1(y As U)
End Interface
Class C1
' Unable to implement because I1.S1 has two identical signatures
Implements I1(Of Integer, Integer)
End Class
Polimorfismo
Il polimorfismo consente di variare l'implementazione di un metodo o di una proprietà. Con il polimorfismo, lo stesso metodo o proprietà può eseguire azioni diverse a seconda del tipo di runtime dell'istanza che lo richiama. I metodi o le proprietà polimorfiche sono denominati sostituibili. Al contrario, l'implementazione di un metodo o di una proprietà non sostituibile è invariante; l'implementazione è identica se il metodo o la proprietà viene richiamata su un'istanza della classe in cui è dichiarata o un'istanza di una classe derivata. Quando viene richiamato un metodo o una proprietà non sostituibile, il tipo in fase di compilazione dell'istanza è il fattore determinante. Per esempio:
Class Base
Public Overridable Property X() As Integer
Get
End Get
Set
End Set
End Property
End Class
Class Derived
Inherits Base
Public Overrides Property X() As Integer
Get
End Get
Set
End Set
End Property
End Class
Module Test
Sub F()
Dim Z As Base
Z = New Base()
Z.X = 10 ' Calls Base.X
Z = New Derived()
Z.X = 10 ' Calls Derived.X
End Sub
End Module
Un metodo sottoponibile a override può anche essere MustOverride, il che significa che non fornisce alcun corpo del metodo e deve essere sottoposto a override.
MustOverride i metodi sono consentiti solo nelle MustInherit classi.
Nell'esempio seguente la classe Shape definisce la nozione astratta di un oggetto forma geometrica che può disegnare se stesso:
MustInherit Public Class Shape
Public MustOverride Sub Paint(g As Graphics, r As Rectangle)
End Class
Public Class Ellipse
Inherits Shape
Public Overrides Sub Paint(g As Graphics, r As Rectangle)
g.drawEllipse(r)
End Sub
End Class
Public Class Box
Inherits Shape
Public Overrides Sub Paint(g As Graphics, r As Rectangle)
g.drawRect(r)
End Sub
End Class
Il Paint metodo è MustOverride dovuto al fatto che non esiste un'implementazione predefinita significativa. Le Ellipse classi e Box sono implementazioni concrete Shape . Poiché queste classi non MustInheritsono , sono necessarie per eseguire l'override del Paint metodo e fornire un'implementazione effettiva.
Si tratta di un errore per un accesso di base per fare riferimento a un MustOverride metodo, come illustrato nell'esempio seguente:
MustInherit Class A
Public MustOverride Sub F()
End Class
Class B
Inherits A
Public Overrides Sub F()
MyBase.F() ' Error, MyBase.F is MustOverride.
End Sub
End Class
Viene segnalato un errore per la MyBase.F() chiamata perché fa riferimento a un MustOverride metodo.
Override dei metodi
Un tipo può eseguire l'override di un metodo sostituibile ereditato dichiarando un metodo con lo stesso nome e , firma e contrassegnando la dichiarazione con il Overrides modificatore. Esistono requisiti aggiuntivi per l'override dei metodi, elencati di seguito. Mentre una Overridable dichiarazione di metodo introduce un nuovo metodo, una Overrides dichiarazione di metodo sostituisce l'implementazione ereditata del metodo .
Un metodo di override può essere dichiarato NotOverridable, che impedisce qualsiasi ulteriore override del metodo nei tipi derivati. In effetti, NotOverridable i metodi diventano non sostituibili in altre classi derivate.
Si consideri l'esempio seguente:
Class A
Public Overridable Sub F()
Console.WriteLine("A.F")
End Sub
Public Overridable Sub G()
Console.WriteLine("A.G")
End Sub
End Class
Class B
Inherits A
Public Overrides NotOverridable Sub F()
Console.WriteLine("B.F")
End Sub
Public Overrides Sub G()
Console.WriteLine("B.G")
End Sub
End Class
Class C
Inherits B
Public Overrides Sub G()
Console.WriteLine("C.G")
End Sub
End Class
Nell'esempio la classe B fornisce due Overrides metodi: un metodo F con il NotOverridable modificatore e un metodo G che non lo fa. L'uso del modificatore impedisce alla classe C di eseguire ulteriormente l'override NotOverridable del metodo F.
Un metodo di override può anche essere dichiarato MustOverride, anche se il metodo di cui esegue l'override non è dichiarato MustOverride. Ciò richiede che la classe contenitore sia dichiarata MustInherit e che qualsiasi altra classe derivata non dichiarata MustInherit deve eseguire l'override del metodo . Per esempio:
Class A
Public Overridable Sub F()
Console.WriteLine("A.F")
End Sub
End Class
MustInherit Class B
Inherits A
Public Overrides MustOverride Sub F()
End Class
Nell'esempio la classe B esegue l'override A.F con un MustOverride metodo . Ciò significa che qualsiasi classe derivata da B dovrà eseguire l'override Fdi , a meno che non vengano dichiarate MustInherit anche.
Si verifica un errore in fase di compilazione, a meno che non siano soddisfatte tutte le condizioni seguenti di un metodo di override:
- Il contesto di dichiarazione contiene un singolo metodo ereditato accessibile con la stessa firma e il tipo restituito (se presente) del metodo di override.
- Il metodo ereditato sottoposto a override è sostituibile. In altre parole, il metodo ereditato sottoposto a override non
Sharedè oNotOverridable. - Il dominio di accessibilità del metodo dichiarato è uguale al dominio di accessibilità del metodo ereditato sottoposto a override. Esiste un'eccezione: un
Protected Friendmetodo deve essere sottoposto a override da unProtectedmetodo se l'altro metodo si trova in un altro assembly a cui il metodo di override non haFriendaccesso. - I parametri del metodo di override corrispondono ai parametri del metodo sottoposto a override per quanto riguarda l'utilizzo
ByValdei modificatori ,ByRefParamArray,eOptional, inclusi i valori forniti per i parametri facoltativi. - I parametri di tipo del metodo di override corrispondono ai parametri di tipo del metodo sottoposto a override per quanto riguarda i vincoli di tipo.
Quando si esegue l'override di un metodo in un tipo generico di base, il metodo di override deve fornire gli argomenti di tipo che corrispondono ai parametri del tipo di base. Per esempio:
Class Base(Of U, V)
Public Overridable Sub M(x As U, y As List(Of V))
End Sub
End Class
Class Derived(Of W, X)
Inherits Base(Of W, X)
' W corresponds to U and X corresponds to V
Public Overrides Sub M(x As W, y As List(Of X))
End Sub
End Class
Class MoreDerived
Inherits Derived(Of String, Integer)
' String corresponds to U and Integer corresponds to V
Public Overrides Sub M(x As String, y As List(Of Integer))
End Sub
End Class
Si noti che è possibile che un metodo sostituibile in una classe generica non possa essere sottoposto a override per alcuni set di argomenti di tipo. Se il metodo è dichiarato MustOverride, ciò significa che alcune catene di ereditarietà potrebbero non essere possibili. Per esempio:
MustInherit Class Base(Of T, U)
Public MustOverride Sub S1(x As T)
Public MustOverride Sub S1(y As U)
End Class
Class Derived
Inherits Base(Of Integer, Integer)
' Error: Can't override both S1's at once
Public Overrides Sub S1(x As Integer)
End Sub
End Class
Una dichiarazione di override può accedere al metodo di base sottoposto a override usando un accesso di base, come nell'esempio seguente:
Class Base
Private x As Integer
Public Overridable Sub PrintVariables()
Console.WriteLine("x = " & x)
End Sub
End Class
Class Derived
Inherits Base
Private y As Integer
Public Overrides Sub PrintVariables()
MyBase.PrintVariables()
Console.WriteLine("y = " & y)
End Sub
End Class
Nell'esempio la chiamata di MyBase.PrintVariables() in classe Derived richiama il PrintVariables metodo dichiarato nella classe Base. Un accesso di base disabilita il meccanismo di chiamata sostituibile e considera semplicemente il metodo di base come metodo non sostituibile. Se la chiamata in Derived è stata scritta CType(Me, Base).PrintVariables(), richiama in modo ricorsivo il PrintVariables metodo dichiarato in Derived, non quello dichiarato in Base.
Solo quando include un Overrides modificatore può eseguire l'override di un altro metodo. In tutti gli altri casi, un metodo con la stessa firma di un metodo ereditato semplicemente ombreggiato dal metodo ereditato, come nell'esempio seguente:
Class Base
Public Overridable Sub F()
End Sub
End Class
Class Derived
Inherits Base
Public Overridable Sub F() ' Warning, shadowing inherited F().
End Sub
End Class
Nell'esempio il metodo F nella classe Derived non include un Overrides modificatore e pertanto non esegue l'override del metodo F nella classe Base. Invece, il metodo F nella classe Derived shadowsa il metodo nella classe Basee viene segnalato un avviso perché la dichiarazione non include un Shadows modificatore o Overloads .
Nell'esempio seguente, il metodo F nella classe Derived shadows il metodo F sottoponibile a override ereditato dalla classe Base:
Class Base
Public Overridable Sub F()
End Sub
End Class
Class Derived
Inherits Base
Private Shadows Sub F() ' Shadows Base.F within Derived.
End Sub
End Class
Class MoreDerived
Inherits Derived
Public Overrides Sub F() ' Ok, overrides Base.F.
End Sub
End Class
Poiché il nuovo metodo F nella classe Derived ha Private accesso, il relativo ambito include solo il corpo della classe di Derived e non si estende alla classe MoreDerived. La dichiarazione del metodo F nella classe MoreDerived è pertanto autorizzata a eseguire l'override del metodo F ereditato dalla classe Base.
Quando viene richiamato un Overridable metodo, viene chiamata l'implementazione più derivata del metodo di istanza, in base al tipo dell'istanza, indipendentemente dal fatto che la chiamata sia al metodo nella classe base o nella classe derivata. L'implementazione più derivata di un Overridable metodo M rispetto a una classe R è determinata nel modo seguente:
Se
Rcontiene la dichiarazione introduttivaOverridablediM, si tratta dell'implementazione più derivata diM.In caso contrario, se
Rcontiene un override diM, si tratta dell'implementazione più derivata diM.In caso contrario, l'implementazione più derivata di
Mè uguale a quella della classe base diretta diR.
Accessibilità
Una dichiarazione specifica l'accessibilità dell'entità dichiarata. L'accessibilità di un'entità non modifica l'ambito del nome di un'entità. Il dominio di accessibilità di una dichiarazione è il set di tutti gli spazi di dichiarazione in cui l'entità dichiarata è accessibile.
I cinque tipi di accesso sono Public, Protected, Friend, Protected Friende Private.
Public è il tipo di accesso più permissivo e gli altri quattro tipi sono tutti subset di Public. Il tipo di accesso meno permissivo è Privatee gli altri quattro tipi di accesso sono tutti superset di Private.
AccessModifier
: 'Public'
| 'Protected'
| 'Friend'
| 'Private'
| 'Protected' 'Friend'
;
Il tipo di accesso per una dichiarazione viene specificato tramite un modificatore di accesso facoltativo, che può essere Public, , ProtectedPrivateFriend, o la combinazione di Protected e .Friend Se non viene specificato alcun modificatore di accesso, il tipo di accesso predefinito dipende dal contesto di dichiarazione; I tipi di accesso consentiti dipendono anche dal contesto di dichiarazione.
Le entità dichiarate con il
Publicmodificatore hannoPublicaccesso. Non esistono restrizioni sull'uso dellePublicentità.Le entità dichiarate con il
Protectedmodificatore hannoProtectedaccesso.ProtectedL'accesso può essere specificato solo sui membri delle classi (sia sui membri di tipo regolare che sulle classi annidate) o suiOverridablemembri di moduli e strutture standard (che devono, per definizione, essere ereditati daSystem.ObjectoSystem.ValueType). UnProtectedmembro è accessibile a una classe derivata, purché il membro non sia un membro dell'istanza o che l'accesso venga eseguito tramite un'istanza della classe derivata.Protectedl'accesso non è un superset diFriendaccesso.Le entità dichiarate con il
Friendmodificatore hannoFriendaccesso. Un'entità conFriendaccesso è accessibile solo all'interno del programma che contiene la dichiarazione di entità o gli assembly a cui è stato concessoFriendl'accesso tramite l'attributoSystem.Runtime.CompilerServices.InternalsVisibleToAttribute.Le entità dichiarate con i
Protected Friendmodificatori hanno l'unione diProtectedeFriendl'accesso.Le entità dichiarate con il
Privatemodificatore hannoPrivateaccesso. Un'entitàPrivateè accessibile solo all'interno del contesto di dichiarazione, incluse le entità annidate.
L'accessibilità in una dichiarazione non dipende dall'accessibilità del contesto di dichiarazione. Ad esempio, un tipo dichiarato con Private accesso può contenere un membro di tipo con Public accesso.
Il codice seguente illustra vari domini di accessibilità:
Public Class A
Public Shared X As Integer
Friend Shared Y As Integer
Private Shared Z As Integer
End Class
Friend Class B
Public Shared X As Integer
Friend Shared Y As Integer
Private Shared Z As Integer
Public Class C
Public Shared X As Integer
Friend Shared Y As Integer
Private Shared Z As Integer
End Class
Private Class D
Public Shared X As Integer
Friend Shared Y As Integer
Private Shared Z As Integer
End Class
End Class
Le classi e i membri di questo esempio hanno i domini di accessibilità seguenti:
Il dominio di accessibilità di
AeA.Xè illimitato.Il dominio di accessibilità di
A.Y,B.XB,B.Y,B.C, ,B.C.XeB.C.Yè il programma contenitore.Il dominio di accessibilità di
A.ZèA.Il dominio di accessibilità di
B.Z,B.D.XB.D, eB.D.YèB, inclusoB.CeB.D.Il dominio di accessibilità di
B.C.ZèB.C.Il dominio di accessibilità di
B.D.ZèB.D.
Come illustrato nell'esempio, il dominio di accessibilità di un membro non è mai maggiore di quello di un tipo contenitore. Ad esempio, anche se tutti i X membri hanno Public dichiarato l'accessibilità, tutti ma A.X hanno domini di accessibilità vincolati da un tipo contenitore.
L'accesso ai membri dell'istanza Protected deve essere tramite un'istanza del tipo derivato in modo che i tipi non correlati non possano ottenere l'accesso ai membri protetti. Per esempio:
Class User
Protected Password As String
End Class
Class Employee
Inherits User
End Class
Class Guest
Inherits User
Public Function GetPassword(u As User) As String
' Error: protected access has to go through derived type.
Return U.Password
End Function
End Class
Nell'esempio precedente la classe Guest ha accesso al campo protetto Password solo se è qualificata con un'istanza di Guest. In questo modo si impedisce Guest di accedere al Password campo di un Employee oggetto semplicemente eseguendo il cast a User.
Ai fini dell'accesso ai membri in tipi generici, il contesto della Protected dichiarazione include parametri di tipo. Ciò significa che un tipo derivato con un set di argomenti di tipo non ha accesso ai Protected membri di un tipo derivato con un set diverso di argomenti di tipo. Per esempio:
Class Base(Of T)
Protected x As T
End Class
Class Derived(Of T)
Inherits Base(Of T)
Public Sub F(y As Derived(Of String))
' Error: Derived(Of T) cannot access Derived(Of String)'s
' protected members
y.x = "a"
End Sub
End Class
Nota. Il linguaggio C# (ed eventualmente altri linguaggi) consente a un tipo generico di accedere ai Protected membri indipendentemente dagli argomenti di tipo forniti. Questo aspetto deve essere tenuto presente quando si progettano classi generiche che contengono Protected membri.
Tipi costitutivi
I tipi costitutivi di una dichiarazione sono i tipi a cui fa riferimento la dichiarazione. Ad esempio, il tipo di una costante, il tipo restituito di un metodo e i tipi di parametro di un costruttore sono tutti tipi costitutivi. Il dominio di accessibilità di un tipo costitutivo di una dichiarazione deve essere uguale a o a un superset del dominio di accessibilità della dichiarazione stessa. Per esempio:
Public Class X
Private Class Y
End Class
' Error: Exposing private class Y outside of X.
Public Function Z() As Y
End Function
' Valid: Not exposing outside of X.
Private Function A() As Y
End Function
End Class
Friend Class B
Private Class C
End Class
' Error: Exposing private class Y outside of B.
Public Function D() As C
End Function
End Class
Nomi dei tipi e degli spazi dei nomi
Per molti costrutti di linguaggio è necessario specificare uno spazio dei nomi o un tipo; questi valori possono essere specificati usando una forma qualificata dello spazio dei nomi o del nome del tipo. Un nome completo è costituito da una serie di identificatori separati da punti; l'identificatore a destra di un punto viene risolto nello spazio di dichiarazione specificato dall'identificatore a sinistra del punto.
Il nome completo di uno spazio dei nomi o di un tipo è un nome completo che contiene il nome di tutti gli spazi dei nomi e i tipi contenenti. In altre parole, il nome completo di uno spazio dei nomi o di un tipo è N.T, dove T è il nome dell'entità e N è il nome completo della relativa entità contenitore.
L'esempio seguente mostra diverse dichiarazioni di spazio dei nomi e tipi insieme ai nomi completi associati nei commenti in linea.
Class A ' A.
End Class
Namespace X ' X.
Class B ' X.B.
Class C ' X.B.C.
End Class
End Class
Namespace Y ' X.Y.
Class D ' X.Y.D.
End Class
End Namespace
End Namespace
Namespace X.Y ' X.Y.
Class E ' X.Y.E.
End Class
End Namespace
Si noti che lo spazio dei nomi X.Y è stato dichiarato in due posizioni diverse nel codice sorgente, ma queste due dichiarazioni parziali costituiscono solo un singolo spazio dei nomi denominato X.Y che contiene sia la classe D che la classe E.
In alcune situazioni, un nome completo può iniziare con la parola chiave Global. La parola chiave rappresenta lo spazio dei nomi più esterno senza nome, utile nelle situazioni in cui una dichiarazione ombreggiata uno spazio dei nomi che lo racchiude. La Global parola chiave consente l'escape allo spazio dei nomi più esterno in tale situazione. Per esempio:
Namespace NS1
Class System
End Class
Module Test
Sub Main()
' Error: Class System does not contain Int32
Dim x As System.Int32
' Legal, binds to System in outermost namespace
Dim y As Global.System.Int32
End Sub
End Module
End Namespace
Nell'esempio precedente la prima chiamata al metodo non è valida perché l'identificatore System viene associato alla classe System, non allo spazio dei nomi System. L'unico modo per accedere allo spazio dei nomi consiste nell'usare System per uscire Global dallo spazio dei nomi più esterno.
Globalnon può essere usato in un'istruzione o Namespace in una Imports dichiarazione.
Poiché altri linguaggi possono introdurre tipi e spazi dei nomi che corrispondono alle parole chiave nel linguaggio, Visual Basic riconosce le parole chiave come parte di un nome completo purché se seguono un punto. Le parole chiave usate in questo modo vengono considerate come identificatori. Ad esempio, l'identificatore completo è un identificatore X.Default.Class completo valido, mentre Default.Class non lo è.
Risoluzione dei nomi qualificata per spazi dei nomi e tipi
Dato uno spazio dei nomi o un nome di tipo completo del modulo N.R(Of A), dove R è l'identificatore più a destra nel nome completo ed A è un elenco di argomenti di tipo facoltativo, i passaggi seguenti descrivono come determinare lo spazio dei nomi o il tipo a cui fa riferimento il nome completo:
Risolvere
N, usando le regole per la risoluzione dei nomi qualificati o non qualificati.Se la risoluzione di
Nnon riesce o si risolve in un parametro di tipo, si verifica un errore in fase di compilazione.In caso contrario, se
Rcorrisponde al nome di uno spazio dei nomi in N e non sono stati forniti argomenti di tipo oppureRcorrisponde a un tipo accessibile inNcon lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, il nome completo fa riferimento a tale spazio dei nomi o tipo.In caso contrario, se
Ncontiene uno o più moduli standard eRcorrisponde al nome di un tipo accessibile con lo stesso numero di parametri di tipo come argomenti di tipo, se presenti, in un modulo standard, il nome completo fa riferimento a tale tipo. SeRcorrisponde al nome dei tipi accessibili con lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, in più moduli standard, si verifica un errore in fase di compilazione.In caso contrario, si verifica un errore in fase di compilazione.
Nota. Un'implicazione di questo processo di risoluzione è che i membri di tipo non shadowno spazi dei nomi o tipi quando si risolvono nomi di spazio dei nomi o tipi.
Risoluzione dei nomi non qualificata per spazi dei nomi e tipi
Dato un nome R(Of A)non qualificato, dove A è un elenco di argomenti di tipo facoltativo, i passaggi seguenti descrivono come determinare lo spazio dei nomi o il tipo a cui fa riferimento il nome non qualificato:
Se R corrisponde al nome di un parametro di tipo del metodo corrente e non sono stati forniti argomenti di tipo, il nome non qualificato fa riferimento a tale parametro di tipo.
Per ogni tipo annidato contenente il riferimento al nome, a partire dal tipo più interno e passando al più esterno:
- Se
Rcorrisponde al nome di un parametro di tipo nel tipo corrente e non sono stati specificati argomenti di tipo, il nome non qualificato fa riferimento a tale parametro di tipo. - In caso contrario, se
Rcorrisponde al nome di un tipo annidato accessibile con lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, il nome non qualificato fa riferimento a tale tipo.
- Se
Per ogni spazio dei nomi annidato contenente il riferimento al nome, a partire dallo spazio dei nomi più interno e passando allo spazio dei nomi più esterno:
- Se
Rcorrisponde al nome di uno spazio dei nomi annidato nello spazio dei nomi corrente e non viene fornito alcun elenco di argomenti di tipo, il nome non qualificato fa riferimento a tale spazio dei nomi annidato. - In caso contrario, se
Rcorrisponde al nome di un tipo accessibile con lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, nello spazio dei nomi corrente, il nome non qualificato fa riferimento a tale tipo. - In caso contrario, se lo spazio dei nomi contiene uno o più moduli standard accessibili e
Rcorrisponde al nome di un tipo annidato accessibile con lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, in un modulo standard, il nome non qualificato fa riferimento a tale tipo annidato. SeRcorrisponde al nome dei tipi annidati accessibili con lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, in più moduli standard, si verifica un errore in fase di compilazione.
- Se
Se il file di origine ha uno o più alias di importazione e
Rcorrisponde al nome di uno di essi, il nome non qualificato fa riferimento a tale alias di importazione. Se viene fornito un elenco di argomenti di tipo, si verifica un errore in fase di compilazione.Se il file di origine contenente il riferimento al nome ha una o più importazioni:
- Se
Rcorrisponde al nome di un tipo accessibile con lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, in un'unica importazione, il nome non qualificato fa riferimento a tale tipo. SeRcorrisponde al nome di un tipo accessibile con lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, in più di un'importazione e tutti non sono dello stesso tipo, si verifica un errore in fase di compilazione. - In caso contrario, se non è stato fornito alcun elenco di argomenti di tipo e
Rcorrisponde al nome di uno spazio dei nomi con tipi accessibili in un'unica importazione, il nome non qualificato fa riferimento a tale spazio dei nomi. Se non è stato fornito alcun elenco di argomenti di tipo eRcorrisponde al nome di uno spazio dei nomi con tipi accessibili in più di un'importazione e tutti non sono lo stesso spazio dei nomi, si verifica un errore in fase di compilazione. - In caso contrario, se le importazioni contengono uno o più moduli standard accessibili e
Rcorrisponde al nome di un tipo annidato accessibile con lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, in un modulo standard, il nome non qualificato fa riferimento a tale tipo. SeRcorrisponde al nome dei tipi annidati accessibili con lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, in più moduli standard, si verifica un errore in fase di compilazione.
- Se
Se l'ambiente di compilazione definisce uno o più alias di importazione e
Rcorrisponde al nome di uno di essi, il nome non qualificato fa riferimento a tale alias di importazione. Se viene fornito un elenco di argomenti di tipo, si verifica un errore in fase di compilazione.Se l'ambiente di compilazione definisce una o più importazioni:
- Se
Rcorrisponde al nome di un tipo accessibile con lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, in un'unica importazione, il nome non qualificato fa riferimento a tale tipo. SeRcorrisponde al nome di un tipo accessibile con lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, in più di un'importazione, si verifica un errore in fase di compilazione. - In caso contrario, se non è stato fornito alcun elenco di argomenti di tipo e
Rcorrisponde al nome di uno spazio dei nomi con tipi accessibili in un'unica importazione, il nome non qualificato fa riferimento a tale spazio dei nomi. Se non è stato specificato alcun elenco di argomenti di tipo eRcorrisponde al nome di uno spazio dei nomi con tipi accessibili in più di un'importazione, si verifica un errore in fase di compilazione. - In caso contrario, se le importazioni contengono uno o più moduli standard accessibili e
Rcorrisponde al nome di un tipo annidato accessibile con lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, in un modulo standard, il nome non qualificato fa riferimento a tale tipo. SeRcorrisponde al nome dei tipi annidati accessibili con lo stesso numero di parametri di tipo degli argomenti di tipo, se presenti, in più moduli standard, si verifica un errore in fase di compilazione.
- Se
In caso contrario, si verifica un errore in fase di compilazione.
Nota. Un'implicazione di questo processo di risoluzione è che i membri di tipo non shadowno spazi dei nomi o tipi quando si risolvono nomi di spazio dei nomi o tipi.
In genere, un nome può verificarsi una sola volta in uno spazio dei nomi specifico. Tuttavia, poiché gli spazi dei nomi possono essere dichiarati in più assembly .NET, è possibile avere una situazione in cui due assembly definiscono un tipo con lo stesso nome completo. In tal caso, un tipo dichiarato nel set corrente di file di origine è preferibile rispetto a un tipo dichiarato in un assembly .NET esterno. In caso contrario, il nome è ambiguo e non esiste alcun modo per disambiguare il nome.
Variabili
Una variabile rappresenta una posizione di archiviazione. Ogni variabile ha un tipo che determina quali valori possono essere archiviati nella variabile. Poiché Visual Basic è un linguaggio indipendente dai tipi, ogni variabile in un programma ha un tipo e il linguaggio garantisce che i valori archiviati nelle variabili siano sempre del tipo appropriato. Le variabili vengono sempre inizializzate sul valore predefinito del tipo prima che sia possibile effettuare qualsiasi riferimento alla variabile. Non è possibile accedere alla memoria non inizializzata.
Tipi e metodi generici
I tipi (ad eccezione dei moduli standard e dei tipi enumerati) e i metodi possono dichiarare parametri di tipo, ovvero tipi che non verranno forniti fino a quando non viene dichiarata un'istanza del tipo o viene richiamato il metodo . I tipi e i metodi con parametri di tipo sono noti rispettivamente come tipi generici e metodi generici, perché il tipo o il metodo deve essere scritto in modo generico, senza conoscere in modo specifico i tipi che verranno forniti dal codice che usa il tipo o il metodo .
Nota. In questo momento, anche se i metodi e i delegati possono essere generici, proprietà, eventi e operatori non possono essere generici. Tuttavia, possono usare parametri di tipo dalla classe contenitore.
Dal punto di vista del tipo o del metodo generico, un parametro di tipo è un tipo segnaposto che verrà compilato con un tipo effettivo quando viene usato il tipo o il metodo. Gli argomenti di tipo vengono sostituiti per i parametri di tipo nel tipo o nel metodo nel punto in cui viene utilizzato il tipo o il metodo. Ad esempio, una classe stack generica può essere implementata come segue:
Public Class Stack(Of ItemType)
Protected Items(0 To 99) As ItemType
Protected CurrentIndex As Integer = 0
Public Sub Push(data As ItemType)
If CurrentIndex = 100 Then
Throw New ArgumentException("Stack is full.")
End If
Items(CurrentIndex) = Data
CurrentIndex += 1
End Sub
Public Function Pop() As ItemType
If CurrentIndex = 0 Then
Throw New ArgumentException("Stack is empty.")
End If
CurrentIndex -= 1
Return Items(CurrentIndex + 1)
End Function
End Class
Le dichiarazioni che usano la Stack(Of ItemType) classe devono fornire un argomento di tipo per il parametro ItemTypedi tipo . Questo tipo viene quindi compilato ovunque ItemType venga usato all'interno della classe :
Option Strict On
Module Test
Sub Main()
Dim s1 As New Stack(Of Integer)()
Dim s2 As New Stack(Of Double)()
s1.Push(10.10) ' Error: Stack(Of Integer).Push takes an Integer
s2.Push(10.10) ' OK: Stack(Of Double).Push takes a Double
Console.WriteLine(s2.Pop().GetType().ToString()) ' Prints: Double
End Sub
End Module
Parametri di tipo
I parametri di tipo possono essere forniti nelle dichiarazioni di tipo o metodo. Ogni parametro di tipo è un identificatore che è un segnaposto per un argomento di tipo fornito per creare un tipo o un metodo costruito. Al contrario, un argomento di tipo è il tipo effettivo che viene sostituito per il parametro di tipo quando viene utilizzato un tipo o un metodo generico.
TypeParameterList
: OpenParenthesis 'Of' TypeParameter ( Comma TypeParameter )* CloseParenthesis
;
TypeParameter
: VarianceModifier? Identifier TypeParameterConstraints?
;
VarianceModifier
: 'In' | 'Out'
;
Ogni parametro di tipo in una dichiarazione di tipo o metodo definisce un nome nello spazio di dichiarazione di tale tipo o metodo. Pertanto, non può avere lo stesso nome di un altro parametro di tipo, un membro di tipo, un parametro del metodo o una variabile locale. L'ambito di un parametro di tipo in un tipo o metodo è l'intero tipo o metodo. Poiché i parametri di tipo hanno come ambito l'intera dichiarazione di tipo, i tipi annidati possono usare parametri di tipo esterno. Ciò significa anche che i parametri di tipo devono essere sempre specificati quando si accede ai tipi annidati all'interno di tipi generici:
Public Class Outer(Of T)
Public Class Inner
Public Sub F(x As T)
...
End Sub
End Class
End Class
Module Test
Sub Main()
Dim x As New Outer(Of Integer).Inner()
...
End Sub
End Module
A differenza di altri membri di una classe, i parametri di tipo non vengono ereditati. I parametri di tipo in un tipo possono essere indicati solo con il nome semplice; in altre parole, non possono essere qualificati con il nome del tipo contenitore. Anche se è uno stile di programmazione non valido, i parametri di tipo in un tipo annidato possono nascondere un membro o un parametro di tipo dichiarato nel tipo esterno:
Class Outer(Of T)
Class Inner(Of T)
Public t1 As T ' Refers to Inner's T
End Class
End Class
I tipi e i metodi possono essere in overload in base al numero di parametri di tipo (o arity) dichiarati dai tipi o dai metodi. Ad esempio, le dichiarazioni seguenti sono legali:
Module C
Sub M()
End Sub
Sub M(Of T)()
End Sub
Sub M(Of T, U)()
End Sub
End Module
Structure C(Of T)
Dim x As T
End Structure
Class C(Of T, U)
End Class
Nel caso dei tipi, gli overload vengono sempre confrontati con il numero di argomenti di tipo specificati. Ciò è utile quando si usano classi generiche e non generiche nello stesso programma:
Class Queue
End Class
Class Queue(Of T)
End Class
Class X
Dim q1 As Queue ' Non-generic queue
Dim q2 As Queue(Of Integer) ' Generic queue
End Class
Le regole per i metodi di overload sui parametri di tipo sono descritte nella sezione relativa alla risoluzione dell'overload del metodo.
All'interno della dichiarazione contenitore, i parametri di tipo sono considerati tipi completi. Poiché è possibile creare un'istanza di un parametro di tipo con molti argomenti di tipo effettivi diversi, i parametri di tipo hanno operazioni e restrizioni leggermente diverse rispetto ad altri tipi, come descritto di seguito:
Un parametro di tipo non può essere usato direttamente per dichiarare una classe o un'interfaccia di base.
Le regole per la ricerca dei membri sui parametri di tipo dipendono dai vincoli, se presenti, applicati al parametro di tipo.
Le conversioni disponibili per un parametro di tipo dipendono dai vincoli, se presenti, applicati ai parametri di tipo.
In assenza di un
Structurevincolo, è possibile confrontare un valore con un tipo rappresentato da un parametro di tipo usandoNothingIseIsNot.Un parametro di tipo può essere usato solo in un'espressione
Newse il parametro di tipo è vincolato da unNewStructurevincolo o .Un parametro di tipo non può essere usato in qualsiasi punto all'interno di un'eccezione di attributo all'interno di un'espressione
GetType.I parametri di tipo possono essere usati come argomenti di tipo per altri tipi e parametri generici.
L'esempio seguente è un tipo generico che estende la Stack(Of ItemType) classe :
Class MyStack(Of ItemType)
Inherits Stack(Of ItemType)
Public ReadOnly Property Size() As Integer
Get
Return CurrentIndex
End Get
End Property
End Class
Quando una dichiarazione fornisce un argomento di tipo a MyStack, verrà applicato anche lo Stack stesso argomento di tipo.
Come tipo, i parametri di tipo sono puramente un costrutto in fase di compilazione. In fase di esecuzione, ogni parametro di tipo è associato a un tipo di runtime specificato fornendo un argomento di tipo alla dichiarazione generica. Di conseguenza, il tipo di una variabile dichiarata con un parametro di tipo sarà, in fase di esecuzione, un tipo non generico o un tipo costruito specifico. L'esecuzione in fase di esecuzione di tutte le istruzioni ed espressioni che coinvolgono parametri di tipo usa il tipo effettivo fornito come argomento di tipo per tale parametro.
Vincoli di tipo
Poiché un argomento di tipo può essere qualsiasi tipo nel sistema di tipi, un tipo o un metodo generico non può fare ipotesi su un parametro di tipo. Pertanto, i membri di un parametro di tipo vengono considerati membri del tipo Object, poiché tutti i tipi derivano da Object.
Nel caso di una raccolta come Stack(Of ItemType), questo fatto potrebbe non essere una restrizione particolarmente importante, ma in alcuni casi un tipo generico può voler fare un presupposto sui tipi che verranno forniti come argomenti di tipo.
I vincoli di tipo possono essere inseriti su parametri di tipo che limitano i tipi che possono essere forniti come parametro di tipo e consentono a tipi o metodi generici di assumere più informazioni sui parametri di tipo.
TypeParameterConstraints
: 'As' Constraint
| 'As' OpenCurlyBrace ConstraintList CloseCurlyBrace
;
ConstraintList
: Constraint ( Comma Constraint )*
;
Constraint
: TypeName
| 'New'
| 'Structure'
| 'Class'
;
Public Class DisposableStack(Of ItemType As IDisposable)
Implements IDisposable
Private _items(0 To 99) As ItemType
Private _currentIndex As Integer = 0
Public Sub Push(data As ItemType)
...
End Sub
Public Function Pop() As ItemType
...
End Function
Private Sub Dispose() Implements IDisposable.Dispose
For Each item As IDisposable In _items
If item IsNot Nothing Then
item.Dispose()
End If
Next item
End Sub
End Class
In questo esempio il DisposableStack(Of ItemType) parametro di tipo viene vincolato solo ai tipi che implementano l'interfaccia System.IDisposable. Di conseguenza, può implementare un Dispose metodo che elimina tutti gli oggetti ancora rimasti nella coda.
Un vincolo di tipo deve essere uno dei vincoli Classspeciali , Structureo Newoppure deve essere un tipo T in cui:
Tè una classe, un'interfaccia o un parametro di tipo.TnonNotInheritableè .Tnon è uno di o un tipo ereditato da uno dei tipi speciali seguenti:System.Array,System.DelegateSystem.MulticastDelegate,System.Enum, oSystem.ValueType.TnonObjectè . Poiché tutti i tipi derivano daObject, tale vincolo non avrebbe alcun effetto se fosse consentito.Tdeve essere almeno accessibile come il tipo o il metodo generico dichiarato.
È possibile specificare più vincoli di tipo per un singolo parametro di tipo racchiudendo i vincoli di tipo tra parentesi graffe ({}).. Un solo vincolo di tipo per un parametro di tipo specificato può essere una classe. Si tratta di un errore per combinare un Structure vincolo speciale con un vincolo di classe denominato o il Class vincolo speciale.
Class ControlFactory(Of T As {Control, New})
...
End Class
I vincoli di tipo possono utilizzare i tipi contenenti o uno qualsiasi dei parametri di tipo dei tipi contenitore. Nell'esempio seguente, il vincolo richiede che l'argomento di tipo fornito implementi un'interfaccia generica usando se stessa come argomento di tipo:
Class Sorter(Of V As IComparable(Of V))
...
End Class
Il vincolo Class di tipo speciale vincola l'argomento di tipo fornito a qualsiasi tipo di riferimento.
Nota. Il vincolo Class di tipo speciale può essere soddisfatto da un'interfaccia. E una struttura può implementare un'interfaccia. Pertanto, il vincolo (Of T As U, U As Class) potrebbe essere soddisfatto con "T" una struttura (che non soddisfa il Class vincolo speciale) e "U" un'interfaccia implementata (che soddisfa il Class vincolo speciale).
Il vincolo Structure di tipo speciale vincola l'argomento di tipo fornito a qualsiasi tipo di valore ad eccezione System.Nullable(Of T)di .
Nota. I vincoli di struttura non consentono System.Nullable(Of T) in modo che non sia possibile fornire System.Nullable(Of T) come argomento di tipo a se stesso.
Il vincolo New di tipo speciale richiede che l'argomento di tipo fornito disponga di un costruttore senza parametri accessibile e che non possa essere dichiarato MustInherit. Per esempio:
Class Factory(Of T As New)
Function CreateInstance() As T
Return New T()
End Function
End Class
Un vincolo di tipo classe richiede che l'argomento di tipo fornito sia tale tipo come o erediti da esso. Un vincolo del tipo di interfaccia richiede che l'argomento del tipo fornito debba implementare tale interfaccia. Un vincolo di parametro di tipo richiede che l'argomento di tipo fornito debba derivare o implementare tutti i limiti specificati per il parametro di tipo corrispondente. Per esempio:
Class List(Of T)
Sub AddRange(Of S As T)(collection As IEnumerable(Of S))
...
End Sub
End Class
In questo esempio, il parametro S di tipo in AddRange è vincolato al parametro T di tipo di List. Ciò significa che un oggetto List(Of Control) vincola AddRangeil parametro di tipo a qualsiasi tipo che è o eredita da Control.
Un vincolo Of S As T di parametro di tipo viene risolto aggiungendo transitivamente tutti i Tvincoli Sdi su , ad eccezione dei vincoli speciali (Class, Structure, New). Si tratta di un errore per avere vincoli circolari ,ad esempio Of S As T, T As S. Si tratta di un errore per avere un vincolo di parametro di tipo che ha il Structure vincolo . Dopo l'aggiunta di vincoli, è possibile che si verifichi una serie di situazioni speciali:
Se esistono più vincoli di classe, la classe più derivata viene considerata il vincolo . Se uno o più vincoli di classe non hanno alcuna relazione di ereditarietà, il vincolo non è soddisfacente e si tratta di un errore.
Se un parametro di tipo combina un
Structurevincolo speciale con un vincolo di classe denominato o ilClassvincolo speciale, si tratta di un errore. Un vincolo di classe può essereNotInheritable, nel qual caso non vengono accettati tipi derivati di tale vincolo ed è un errore.
Il tipo può essere uno di o un tipo ereditato da, i tipi speciali seguenti: System.Array, System.DelegateSystem.MulticastDelegate, System.Enum, o System.ValueType. In tal caso, viene accettato solo il tipo o un tipo ereditato da esso. Un parametro di tipo vincolato a uno di questi tipi può usare solo le conversioni consentite dall'operatore DirectCast . Per esempio:
MustInherit Class Base(Of T)
MustOverride Sub S1(Of U As T)(x As U)
End Class
Class Derived
Inherits Base(Of Integer)
' The constraint of U must be Integer, which is normally not allowed.
Overrides Sub S1(Of U As Integer)(x As U)
Dim y As Integer = x ' OK
Dim z As Long = x ' Error: Can't convert
End Sub
End Class
Inoltre, un parametro di tipo vincolato a un tipo valore a causa di uno dei relax precedenti non può chiamare metodi definiti su tale tipo di valore. Per esempio:
Class C1(Of T)
Overridable Sub F(Of G As T)(x As G)
End Sub
End Class
Class C2
Inherits C1(Of IntPtr)
Overrides Sub F(Of G As IntPtr)(ByVal x As G)
' Error: Cannot access structure members
x.ToInt32()
End Sub
End Class
Se il vincolo, dopo la sostituzione, termina come tipo di matrice, è consentito anche qualsiasi tipo di matrice covariante. Per esempio:
Module Test
Class B
End Class
Class D
Inherits B
End Class
Function F(Of T, U As T)(x As U) As T
Return x
End Function
Sub Main()
Dim a(9) As B
Dim b(9) As D
a = F(Of B(), D())(b)
End Sub
End Module
Un parametro di tipo con una classe o un vincolo di interfaccia viene considerato avere gli stessi membri di tale classe o vincolo di interfaccia. Se un parametro di tipo ha più vincoli, il parametro di tipo viene considerato come un'unione di tutti i membri dei vincoli. Se sono presenti membri con lo stesso nome in più vincoli, i membri vengono nascosti nell'ordine seguente: il vincolo di classe nasconde i membri nei vincoli di interfaccia, che nascondono i membri in System.ValueType (se Structure viene specificato il vincolo), che nasconde i membri in Object. Se un membro con lo stesso nome viene visualizzato in più vincoli di interfaccia, il membro non è disponibile (come in più ereditarietà dell'interfaccia) e il parametro di tipo deve essere sottoposto a cast all'interfaccia desiderata. Per esempio:
Class C1
Sub S1(x As Integer)
End Sub
End Class
Interface I1
Sub S1(x As Integer)
End Interface
Interface I2
Sub S1(y As Double)
End Interface
Module Test
Sub T1(Of T As {C1, I1, I2})()
Dim a As T
a.S1(10) ' Calls C1.S1, which is preferred
a.S1(10.10) ' Also calls C1.S1, class is still preferred
End Sub
Sub T2(Of T As {I1, I2})()
Dim a As T
a.S1(10) ' Error: Call is ambiguous between I1.S1, I2.S1
End Sub
End Module
Quando si specificano parametri di tipo come argomenti di tipo, i parametri di tipo devono soddisfare i vincoli dei parametri di tipo corrispondenti.
Class Base(Of T As Class)
End Class
Class Derived(Of V)
' Error: V does not satisfy the constraints of T
Inherits Base(Of V)
End Class
I valori di un parametro di tipo vincolato possono essere usati per accedere ai membri dell'istanza, inclusi i metodi di istanza, specificati nel vincolo .
Interface IPrintable
Sub Print()
End Interface
Class Printer(Of V As IPrintable)
Sub PrintOne(v1 As V)
V1.Print()
End Sub
End Class
Varianza dei parametri di tipo
Un parametro di tipo in un'interfaccia o una dichiarazione di tipo delegato può facoltativamente specificare un modificatore di varianza. I parametri di tipo con modificatori di varianza limitano la modalità di utilizzo del parametro di tipo nell'interfaccia o nel tipo delegato, ma consentono di convertire un'interfaccia generica o un tipo delegato in un altro tipo generico con argomenti di tipo compatibili con variant. Per esempio:
Class Base
End Class
Class Derived
Inherits Base
End Class
Module Test
Sub Main()
Dim x As IEnumerable(Of Derived) = ...
' OK, as IEnumerable(Of Base) is variant compatible
' with IEnumerable(Of Derived)
Dim y As IEnumerable(Of Base) = x
End Sub
End Module
Le interfacce generiche con parametri di tipo con modificatori di varianza presentano diverse restrizioni:
Non possono contenere una dichiarazione di evento che specifica un elenco di parametri ( ma è consentita una dichiarazione di evento personalizzata o una dichiarazione di evento con un tipo delegato).
Non possono contenere una classe, una struttura o un tipo enumerato annidato.
Nota. Queste restrizioni sono dovute al fatto che i tipi annidati nei tipi generici copiano in modo implicito i parametri generici del relativo elemento padre. Nel caso di classi, strutture o tipi enumerati annidati, tali tipi di tipi non possono avere modificatori di varianza sui relativi parametri di tipo. Nel caso di una dichiarazione di evento con un elenco di parametri, la classe delegato nidificata generata potrebbe avere errori confusi quando un tipo che sembra essere usato in una In posizione (ad esempio un tipo di parametro) viene effettivamente usato in una Out posizione (ad esempio il tipo dell'evento).
Un parametro di tipo dichiarato con il modificatore Out è covariante. In modo informale, un parametro di tipo covariante può essere usato solo in una posizione di output, ovvero un valore restituito dall'interfaccia o dal tipo delegato, e non può essere usato in una posizione di input. Un tipo T è considerato valido in modo covariante se:
Tè una classe, una struttura o un tipo enumerato.Tè un tipo di interfaccia o delegato non generico.Tè un tipo di matrice il cui tipo di elemento è valido in modo covariante.Tè un parametro di tipo che non è stato dichiarato comeOutparametro di tipo.Tè un'interfaccia costruita o un tipo delegato con argomentiA1,...,AndiX(Of P1,...,Pn)tipo in modo che:Se
Piè stato dichiarato come parametro di tipo Out,Aiè valido in modo covariante.Se
Piè stato dichiarato come parametro di tipo In,Aiè valido in modo controvariante.
Il codice seguente deve essere valido in modo covariante in un'interfaccia o in un tipo delegato:
Interfaccia di base di un'interfaccia.
Tipo restituito di una funzione o del tipo delegato.
Tipo di una proprietà se è presente una
Getfunzione di accesso.Tipo di qualsiasi
ByRefparametro.
Per esempio:
Delegate Function D(Of Out T, U)(x As U) As T
Interface I1(Of Out T)
End Interface
Interface I2(Of Out T)
Inherits I1(Of T)
' OK, T is only used in an Out position
Function M1(x As I1(Of T)) As T
' Error: T is used in an In position
Function M2(x As T) As T
End Interface
Nota.
Out non è una parola riservata.
Un parametro di tipo dichiarato con il modificatore In è controvariante. In modo informale, un parametro di tipo controvariante può essere usato solo in una posizione di input, ovvero un valore passato all'interfaccia o al tipo delegato, e non può essere usato in una posizione di output. Un tipo T viene considerato valido in modo controvariante se:
Tè una classe, una struttura o un tipo enumerato.Tè un tipo di interfaccia o delegato non generico.Tè un tipo di matrice il cui tipo di elemento è valido in modo controvariante.Tè un parametro di tipo che non è stato dichiarato come parametro di tipo In.Tè un'interfaccia costruita o un tipo delegato con argomentiA1,...,AndiX(Of P1,...,Pn)tipo in modo che:Se
Piè stato dichiarato comeOutparametro di tipo,Aiè valido in modo controvariante.Se
Piè stato dichiarato comeInparametro di tipo,Aiè valido in modo covariante.
Il codice seguente deve essere valido in modo controvariante in un'interfaccia o in un tipo delegato:
Tipo di un parametro.
Vincolo di tipo su un parametro del tipo di metodo.
Tipo di una proprietà se dispone di una
Setfunzione di accesso.Tipo di un evento.
Per esempio:
Delegate Function D(Of T, In U)(x As U) As T
Interface I1(Of In T)
End Interface
Interface I2(Of In T)
' OK, T is only used in an In position
Sub M1(x As I1(Of T))
' Error: T is used in an Out position
Function M2() As T
End Interface
Nel caso in cui un tipo deve essere valido in modo controvariante e covariante (ad esempio una proprietà con una Get funzione di accesso e Set o un parametro), non è possibile usare un ByRef parametro di tipo variant.
Co- e contro-varianza danno origine a un "problema di ambiguità rombo". Osservare il codice seguente:
Class C
Implements IEnumerable(Of String)
Implements IEnumerable(Of Exception)
Public Function GetEnumerator1() As IEnumerator(Of String) _
Implements IEnumerable(Of String).GetEnumerator
Console.WriteLine("string")
End Function
Public Function GetEnumerator2() As IEnumerator(Of Exception) _
Implements IEnumerable(Of Execption).GetEnumerator
Console.WriteLine("exception")
End Function
End Class
Dim c As IEnumerable(Of Object) = New C
c.GetEnumerator()
La classe C può essere convertita IEnumerable(Of Object) in in due modi, sia tramite la conversione covariante da IEnumerable(Of String) che tramite la conversione covariante da IEnumerable(Of Exception). CLR non specifica quale dei due metodi verrà chiamato da c.GetEnumerator(). In generale, ogni volta che una classe viene dichiarata per implementare un'interfaccia covariante con due argomenti generici diversi che hanno un supertipo comune (ad esempio, in questo caso String e Exception hanno il supertipo Objectcomune ), o una classe viene dichiarata per implementare un'interfaccia controvariante con due argomenti generici diversi che hanno un sottotipo comune, quindi è probabile che si verifichino ambiguità. Il compilatore visualizza un avviso su tali dichiarazioni.
Visual Basic language spec