Allgemeine Konzepte

Dieses Kapitel behandelt eine Reihe von Konzepten, die erforderlich sind, um die Semantik der Microsoft Visual Basic-Sprache zu verstehen. Viele der Konzepte sollten visual Basic-Programmierern oder C/C++-Programmierern vertraut sein, ihre genauen Definitionen können sich jedoch unterscheiden.

Deklarationen

Ein Visual Basic-Programm besteht aus benannten Entitäten. Diese Entitäten werden durch Deklarationen eingeführt und stellen die "Bedeutung" des Programms dar.

Auf oberster Ebene sind Namespaces Entitäten , die andere Entitäten organisieren, z. B. geschachtelte Namespaces und Typen. Typen sind Entitäten, die Werte beschreiben und ausführbaren Code definieren. Typen können geschachtelte Typen und Typmber enthalten. Typmember sind Konstanten, Variablen, Methoden, Operatoren, Eigenschaften, Ereignisse, Enumerationswerte und Konstruktoren.

Eine Entität, die andere Entitäten enthalten kann, definiert einen Deklarationsraum. Entitäten werden entweder über Deklarationen oder Vererbung in ein Deklarationsraum eingeführt; der enthaltende Deklarationsraum wird als Deklarationskontext der Entitäten bezeichnet. Durch das Deklarieren einer Entität in einem Deklarationsraum wird wiederum ein neuer Deklarationsbereich definiert, der weitere geschachtelte Entitätsdeklarationen enthalten kann; somit bilden die Deklarationen in einem Programm eine Hierarchie von Deklarationsräumen.

Außer im Falle von überladenen Typmitgliedern ist es ungültig, dass Deklarationen identisch benannte Entitäten derselben Art in den gleichen Deklarationskontext einführen. Darüber hinaus darf ein Deklarationsraum niemals unterschiedliche Arten von Entitäten mit demselben Namen enthalten; Beispielsweise kann ein Deklarationsraum niemals eine Variable und eine Methode mit demselben Namen enthalten.

Hinweis: Es kann in anderen Sprachen möglich sein, ein Deklarationsraum zu erstellen, der unterschiedliche Arten von Entitäten mit demselben Namen enthält (z. B. wenn die Sprache groß- und kleinschreibungsempfindlich ist und unterschiedliche Deklarationen basierend auf der Groß-/Kleinschreibung zulässt). In dieser Situation gilt die barrierefreiste Entität als gebunden an diesen Namen; Wenn auf mehrere Entitätstypen am meisten zugegriffen werden kann, ist der Name mehrdeutig. Public ist barrierefreier als Protected Friend, Protected Friend ist barrierefreier als Protected oder Friend, und Protected oder Friend ist barrierefreier als Private.

Der Deklarationsraum eines Namespaces ist "geöffnet beendet", sodass zwei Namespacedeklarationen mit demselben vollqualifizierten Namen zum gleichen Deklarationsraum beitragen. Im folgenden Beispiel tragen die beiden Namespacedeklarationen zum gleichen Deklarationsraum bei, in diesem Fall zwei Klassen mit den vollqualifizierten Namen Data.Customer und Data.Order:

Namespace Data
    Class Customer
    End Class
End Namespace

Namespace Data
    Class Order
    End Class
End Namespace

Da die beiden Deklarationen zum gleichen Deklarationsbereich beitragen, würde ein Kompilierungszeitfehler auftreten, wenn jede eine Deklaration einer Klasse mit demselben Namen enthielt.

Überladung und Signaturen

Die einzige Möglichkeit, identisch benannte Entitäten derselben Art in einem Deklarationsraum zu deklarieren, erfolgt überladen. Es können nur Methoden, Operatoren, Instanzkonstruktoren und Eigenschaften überladen werden.

Überladene Typenmber müssen über eindeutige Signaturen verfügen. Die Signatur eines Typelements besteht aus der Anzahl der Typparameter sowie der Anzahl und den Typen der Memberparameter. Konvertierungsoperatoren enthalten auch den Rückgabetyp des Operators in der Signatur.

Es folgt kein Teil der Signatur eines Mitglieds und kann daher nicht überladen werden:The following are not part of a member's signature, and daher cannot be overloaded on:

  • Modifizierer zu einem Typmemm (z. B Shared . oder Private).

  • Modifizierer zu einem Parameter (z. B ByVal . oder ByRef).

  • Die Namen der Parameter.

  • Der Rückgabetyp einer Methode oder eines Operators (mit Ausnahme von Konvertierungsoperatoren) oder der Elementtyp einer Eigenschaft.

  • Einschränkungen für einen Typparameter.

Das folgende Beispiel zeigt eine Reihe überladener Methodendeklarationen zusammen mit ihren Signaturen. Diese Deklaration wäre ungültig, da mehrere Methodendeklarationen identische Signaturen aufweisen.

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

Es ist gültig, einen generischen Typ zu definieren, der Elemente mit identischen Signaturen basierend auf den angegebenen Typargumenten enthalten kann. Überladungsauflösungsregeln werden verwendet, um zwischen solchen Überladungen zu versuchen und zu disambiguieren, obwohl es situationen geben kann, in denen es unmöglich ist, mehrdeutig zu sein. Beispiel:

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

Geltungsbereich

Der Bereich des Namens einer Entität ist der Satz aller Deklarationszeichen, in denen es möglich ist, ohne Qualifikation auf diesen Namen zu verweisen. Im Allgemeinen ist der Umfang des Namens einer Entität der gesamte Deklarationskontext; Die Deklaration einer Entität kann jedoch geschachtelte Deklarationen von Entitäten mit demselben Namen enthalten. In diesem Fall sind die geschachtelten Entitätsschatten oder ausgeblendet, die äußere Entität und der Zugriff auf die schattierte Entität nur durch Qualifizierung möglich.

Das Schattieren durch Schachtelung erfolgt in Namespaces oder Typen, die in Namespaces geschachtelt sind, in Typen, die in anderen Typen geschachtelt sind, und in den Textkörpern der Typmmber. Das Schattieren durch die Schachtelung von Deklarationen erfolgt immer implizit; Es ist keine explizite Syntax erforderlich.

Im folgenden Beispiel wird die Instanzvariable innerhalb der F Methode von der lokalen Variablen ischattiert, aber innerhalb der G Methode i bezieht sich immer noch auf die Instanzvariable.i

Class Test
    Private i As Integer = 0

    Sub F()
        Dim i As Integer = 1
    End Sub

    Sub G()
        i = 1
    End Sub
End Class

Wenn ein Name in einem inneren Bereich einen Namen in einem äußeren Bereich ausblendet, schattiert er alle überladenen Vorkommen dieses Namens. Im folgenden Beispiel ruft der Aufruf den F deklarierten Inner Aufruf F(1) auf, da alle äußeren Vorkommen der inneren F Deklaration ausgeblendet werden. Aus demselben Grund ist der Aufruf F("Hello") fehlerhaft.

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

Vererbung

Eine Vererbungsbeziehung ist eine, bei der ein Typ (der abgeleitete Typ) von einem anderen (dem Basistyp ) abgeleitet wird, sodass der Deklarationsbereich des abgeleiteten Typs implizit die nicht Konstruktortypmber und geschachtelten Typen seines Basistyps enthält. Im folgenden Beispiel ist die Klasse A die Basisklasse von Bund B wird von A.

Class A
End Class

Class B
    Inherits A
End Class

Da A keine Basisklasse explizit angegeben wird, wird die Basisklasse implizit Objectangegeben.

Im Folgenden sind wichtige Aspekte der Vererbung aufgeführt:

  • Vererbung ist transitiv. Wenn Typ C vom Typ B abgeleitet ist und Typ B vom Typ A abgeleitet wird, erbt Typ C die typmember, die in Typ B deklariert sind, sowie die Typmember, die in Typ A deklariert sind.

  • Ein abgeleiteter Typ erweitert den Basistyp, kann aber nicht schmal sein. Ein abgeleiteter Typ kann neue Typmember hinzufügen, und er kann geerbte Typmember schattenn, aber die Definition eines geerbten Typmembers kann nicht entfernt werden.

  • Da eine Instanz eines Typs alle Typmember des Basistyps enthält, ist eine Konvertierung immer von einem abgeleiteten Typ in den Basistyp vorhanden.

  • Alle Typen müssen über einen Basistyp verfügen, mit Ausnahme des Typs Object. Object Daher ist der ultimative Basistyp aller Typen, und alle Typen können in sie konvertiert werden.

  • Zirkularität der Ableitung ist nicht zulässig. Das heißt, wenn ein Typ B von einem Typ Aabgeleitet wird, handelt es sich um einen Fehler für den Typ A , der direkt oder indirekt vom Typ Babgeleitet werden soll.

  • Ein Typ kann nicht direkt oder indirekt von einem darin geschachtelten Typ abgeleitet werden.

Im folgenden Beispiel wird ein Kompilierungszeitfehler erzeugt, da die Klassen zirkulär voneinander abhängen.

Class A
    Inherits B
End Class

Class B
    Inherits C
End Class

Class C
    Inherits A
End Class

Im folgenden Beispiel wird auch ein Kompilierungszeitfehler erzeugt, da B er indirekt von der geschachtelten Klasse C über die Klasse Aabgeleitet wird.

Class A
    Inherits B.C
End Class

Class B
    Inherits A

    Public Class C
    End Class 
End Class

Im nächsten Beispiel wird kein Fehler erzeugt, da die Klasse A nicht von der Klasse Babgeleitet wird.

Class A
    Class B
        Inherits A
    End Class 
End Class

MustInherit- und NotInheritable-Klassen

Eine MustInherit Klasse ist ein unvollständiger Typ, der nur als Basistyp fungieren kann. Eine MustInherit Klasse kann nicht instanziiert werden, sodass es sich um einen Fehler handelt, um den New Operator auf einer Instanz zu verwenden. Es ist gültig, Variablen von MustInherit Klassen zu deklarieren. Solche Variablen können nur zugewiesen Nothing werden oder ein Wert, der von der MustInherit Klasse abgeleitet ist.

Wenn eine reguläre Klasse von einer MustInherit Klasse abgeleitet wird, muss die reguläre Klasse alle geerbten MustOverride Member überschreiben. Beispiel:

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

Die MustInherit Klasse A führt eine MustOverride Methode Fein. Klasse B führt eine zusätzliche Methode Gein, bietet jedoch keine Implementierung von F. Klasse B muss daher auch deklariert MustInheritwerden. Die Klasse C überschreibt F und stellt eine konkrete Implementierung bereit. Da es keine herausragenden MustOverride Member in der Klasse Cgibt, ist es nicht erforderlich, MustInherit.

Eine NotInheritable Klasse ist eine Klasse, von der eine andere Klasse nicht abgeleitet werden kann. NotInheritable Klassen werden in erster Linie verwendet, um unbeabsichtigte Ableitungen zu verhindern.

In diesem Beispiel ist die Klasse B fehlerhaft, da sie versucht, von der NotInheritable Klasse Aabzuleiten. Eine Klasse kann nicht sowohl als auch MustInheritNotInheritablenicht markiert werden.

NotInheritable Class A
End Class

Class B
    ' Error, a class cannot derive from a NotInheritable class.
    Inherits A
End Class

Schnittstellen und mehrfache Vererbung

Im Gegensatz zu anderen Typen, die nur von einem einzelnen Basistyp abgeleitet sind, kann eine Schnittstelle von mehreren Basisschnittstellen abgeleitet werden. Aus diesem Grund kann eine Schnittstelle ein identisch benanntes Typmember von verschiedenen Basisschnittstellen erben. In einem solchen Fall steht der multivererbte Name nicht in der abgeleiteten Schnittstelle zur Verfügung, und das Verweisen auf einen dieser Typenmember über die abgeleitete Schnittstelle verursacht unabhängig von Signaturen oder Überladungen einen Kompilierungszeitfehler. Stattdessen müssen konfliktierende Typenmember über einen Basisschnittstellennamen referenziert werden.

Im folgenden Beispiel verursachen die ersten beiden Anweisungen Kompilierungszeitfehler, da das multivererbte Element Count in der Schnittstelle IListCounternicht verfügbar ist:

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

Wie im Beispiel dargestellt, wird die Mehrdeutigkeit durch Umwandlung x in den entsprechenden Basisschnittstellentyp aufgelöst. Solche Umwandlungen haben keine Laufzeitkosten; sie bestehen lediglich darin, die Instanz zur Kompilierungszeit als weniger abgeleiteten Typ anzuzeigen.

Wenn ein einzelnes Typmember von derselben Basisschnittstelle über mehrere Pfade geerbt wird, wird das Typmember so behandelt, als ob es nur einmal geerbt wurde. Anders ausgedrückt: Die abgeleitete Schnittstelle enthält nur eine Instanz jedes Typmembers, das von einer bestimmten Basisschnittstelle geerbt wurde. Beispiel:

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

Wenn ein Typelementname in einem Pfad durch die Vererbungshierarchie schattiert wird, wird der Name in allen Pfaden schattiert. Im folgenden Beispiel wird das IBase.F Element vom ILeft.F Element schattiert, ist jedoch nicht schattiert 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

Der Aufruf d.F(1) wählt aus ILeft.F, auch wenn IBase.F er nicht in dem Zugriffspfad schattiert wird, der durch IRightführt. Da der Zugriffspfad von zu IBaseILeft Schatten IBase.FreichtIDerived, wird das Element auch im Zugriffspfad von IDerived zu " IBasenachIRight" schattiert.

Shadowing

Ein abgeleiteter Typ schattiert den Namen eines geerbten Typmembers, indem es erneut deklariert wird. Durch das Schattieren eines Namens werden die geerbten Typmember nicht mit diesem Namen entfernt. es macht lediglich alle geerbten Typm mit diesem Namen in der abgeleiteten Klasse nicht verfügbar. Die Schattendeklaration kann eine beliebige Art von Entität sein.

Entitäten, als überladen werden können, können eine von zwei Formen der Schattenung auswählen. Die Schattierung anhand des Namens wird mithilfe des Shadows Schlüsselworts angegeben. Eine Entität, die nach Namen schattiert, blendet alles anhand dieses Namens in der Basisklasse aus, einschließlich aller Überladungen. Der Schatten nach Name und Signatur wird mithilfe des Overloads Schlüsselworts angegeben. Eine Entität, die nach Name und Signatur schattiert, blendet alles mit diesem Namen mit derselben Signatur wie die Entität aus. Beispiel:

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

Durch das Schattieren einer Methode mit einem ParamArray Argument nach Name und Signatur werden nur die einzelne Signatur, nicht alle möglichen erweiterten Signaturen, ausgeblendet. Dies gilt auch, wenn die Signatur der Schattenmethode mit der nicht erweiterten Signatur der schattierten Methode übereinstimmt. Das folgende Beispiel:

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

druckt Base, obwohl Derived.F die gleiche Signatur wie die unexpandierte Form von Base.F.

Umgekehrt, eine Methode mit einem ParamArray Argument nur Schattenmethoden mit derselben Signatur, nicht alle möglichen erweiterten Signaturen. Das folgende Beispiel:

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

druckt , obwohl ein erweitertes BaseFormular mit der gleichen Signatur wie Base.F.Derived.F

Eine Schattenmethode oder -eigenschaft, die nicht angibt Shadows oder Overloads davon ausgeht Overloads , ob die Methode oder Eigenschaft deklariert Overridesist, Shadows andernfalls. Wenn ein Element einer Reihe überladener Entitäten das Shadows oder Overloads das Schlüsselwort angibt, müssen sie alle angeben. Die Shadows Schlüsselwörter können Overloads nicht gleichzeitig angegeben werden. Weder ShadowsOverloads noch kann in einem Standardmodul angegeben werden; Elemente in einem Standardmodul erben Objectimplizit Schattenmember.

Es ist gültig, den Namen eines Typmembers abzuschatten, der durch die Schnittstellenvererbung multipliziert wurde (und damit nicht verfügbar ist), wodurch der Name in der abgeleiteten Schnittstelle verfügbar gemacht wird.

Beispiel:

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

Da Methoden geerbten Methoden schattenn dürfen, ist es möglich, dass eine Klasse mehrere Overridable Methoden mit derselben Signatur enthält. Dies stellt kein Mehrdeutigkeitsproblem dar, da nur die am häufigsten abgeleitete Methode sichtbar ist. Im folgenden Beispiel enthalten die C Klassen zwei DOverridable Methoden mit derselben Signatur:

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

Es gibt hier zwei Overridable Methoden: eine von Klasse A und die von Klasse Ceingeführte. Die von der Klasse C eingeführte Methode blendet die von der Klasse Ageerbte Methode aus. Daher überschreibt die Overrides Deklaration in der Klasse D die von der Klasse Ceingeführte Methode, und es ist nicht möglich D , die von der Klasse Aeingeführte Methode außer Kraft zu setzen. Das Beispiel ergibt die Ausgabe:

B.F
B.F
D.F
D.F

Es ist möglich, die ausgeblendete Overridable Methode durch Den Zugriff auf eine Instanz der Klasse D über einen weniger abgeleiteten Typ aufzurufen, in dem die Methode nicht ausgeblendet ist.

Es ist nicht gültig, eine MustOverride Methode abzuschatten, da dies in den meisten Fällen die Klasse unbrauchbar machen würde. Beispiel:

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 diesem Fall ist die Klasse MoreDerived erforderlich, um die MustOverride Methode Base.Faußer Kraft zu setzen, aber da die Klassenschatten DerivedBase.Fnicht möglich sind. Es gibt keine Möglichkeit, einen gültigen absteigenden Wert von Derived.

Im Gegensatz zum Schattieren eines Namens aus einem äußeren Bereich bewirkt das Schattieren eines barrierefreien Namens aus einem geerbten Bereich, dass eine Warnung gemeldet wird, wie im folgenden Beispiel gezeigt:

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

Die Deklaration der Methode F in der Klasse Derived bewirkt, dass eine Warnung gemeldet wird. Das Abschatten eines geerbten Namens ist ausdrücklich kein Fehler, da dies eine separate Entwicklung von Basisklassen ausschließen würde. Die oben genannte Situation könnte beispielsweise auftreten, weil eine spätere Version der Klasse Base eine Methode F eingeführt hat, die in einer früheren Version der Klasse nicht vorhanden war. Wenn die obige Situation ein Fehler war, könnte jede Änderung an einer Basisklasse in einer separat versionsierten Klassenbibliothek möglicherweise dazu führen, dass abgeleitete Klassen ungültig werden.

Die Warnung, die durch Das Schattieren eines geerbten Namens verursacht wird, kann durch die Verwendung des Shadows Modifizierers Overloads eliminiert werden:

Class Base
    Public Sub F()
    End Sub 
End Class 

Class Derived
    Inherits Base

    Public Shadows Sub F() 'OK.
    End Sub
End Class

Der Shadows Modifizierer gibt die Absicht an, das geerbte Element abzuschatten. Es handelt sich nicht um einen Fehler, den Shadows oder Overloads den Modifizierer anzugeben, wenn kein Typmemembname für Schatten vorhanden ist.

Eine Deklaration eines neuen Members schattiert ein geerbtes Element nur innerhalb des Gültigkeitsbereichs des neuen Elements, wie im folgenden Beispiel gezeigt:

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

Im obigen Beispiel schattiert die Deklaration der Methode F in klassenschatten Derived die Methode F , die von der Klasse Basegeerbt wurde, aber da die neue Methode F in der Klasse Derived Zugriff hat Private , wird der Bereich nicht auf die Klasse MoreDerivederweitert. Daher ist der Aufruf F()MoreDerived.G gültig und wird aufgerufen Base.F. Bei überladenen Typmmbern wird der gesamte Satz überladener Typmber so behandelt, als hätten sie alle den zulässigsten Zugriff für die Zwecke der Schattierung.

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 diesem Beispiel wird die überladene F(Integer) Deklaration mit Zugriff deklariert, obwohl die In-Deklaration DerivedF() mit PrivatePublic Zugriff deklariert wird. Daher wird der Name FDerived im Sinne der Schattierung so behandelt, als wäre Publices , also beide Methoden schatten F in Base.

Implementierung

Eine Implementierungsbeziehung ist vorhanden, wenn ein Typ deklariert, dass er eine Schnittstelle implementiert, und der Typ implementiert alle Typmember der Schnittstelle. Ein Typ, der eine bestimmte Schnittstelle implementiert, ist in diese Schnittstelle konvertierbar. Schnittstellen können nicht instanziiert werden, aber sie ist gültig, um Variablen von Schnittstellen zu deklarieren; solchen Variablen kann nur ein Wert zugewiesen werden, der eine Klasse ist, die die Schnittstelle implementiert. Beispiel:

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

Ein Typ, der eine Schnittstelle mit multivererbten Typmember implementiert, muss diese Methoden dennoch implementieren, obwohl nicht direkt von der abgeleiteten Schnittstelle aus zugegriffen werden kann, die implementiert wird. Beispiel:

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

Selbst MustInherit Klassen müssen Implementierungen aller Member der implementierten Schnittstellen bereitstellen. Sie können jedoch die Implementierung dieser Methoden zurückstellen, indem sie sie deklarieren als MustOverride. Beispiel:

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

Ein Typ kann eine Schnittstelle, die vom Basistyp implementiert wird, erneut implementieren. Um die Schnittstelle erneut zu implementieren, muss der Typ explizit angeben, dass sie die Schnittstelle implementiert. Ein Typ, der eine Schnittstelle erneut implementiert, kann nur einige, aber nicht alle Member der Schnittstelle erneut implementieren – alle Elemente, die nicht erneut implementiert werden, verwenden weiterhin die Implementierung des Basistyps. Beispiel:

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

In diesem Beispiel wird gedruckt:

TestDerived.DerivedTest1
TestBase.Test2

Wenn ein abgeleiteter Typ eine Schnittstelle implementiert, deren Basisschnittstellen von den Basistypen des abgeleiteten Typs implementiert werden, kann der abgeleitete Typ auswählen, dass nur die Typmember der Schnittstelle implementiert werden, die noch nicht von den Basistypen implementiert werden. Beispiel:

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

Eine Schnittstellenmethode kann auch mithilfe einer überschreibbaren Methode in einem Basistyp implementiert werden. In diesem Fall kann ein abgeleiteter Typ auch die überschreibbare Methode außer Kraft setzen und die Implementierung der Schnittstelle ändern. Beispiel:

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

Implementieren von Methoden

Ein Typ implementiert ein Typmemm einer implementierten Schnittstelle, indem eine Methode mit einer Implements Klausel bereitgestellt wird. Die beiden Typenmember müssen dieselbe Anzahl von Parametern aufweisen, alle Typen und Modifizierer der Parameter müssen übereinstimmen, einschließlich des Standardwerts optionaler Parameter, der Rückgabetyp muss übereinstimmen, und alle Einschränkungen für Methodenparameter müssen übereinstimmen. Beispiel:

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

Eine einzelne Methode kann eine beliebige Anzahl von Schnittstellentypmember implementieren, wenn sie alle die oben genannten Kriterien erfüllen. Beispiel:

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

Bei der Implementierung einer Methode in einer generischen Schnittstelle muss die Implementierungsmethode die Typargumente angeben, die den Typparametern der Schnittstelle entsprechen. Beispiel:

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

Beachten Sie, dass es möglich ist, dass eine generische Schnittstelle für einige Typenargumente möglicherweise nicht implementiert werden kann.

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

Polymorphismus

Polymorphismus bietet die Möglichkeit, die Implementierung einer Methode oder Eigenschaft zu variieren. Bei Polymorphismus kann die gleiche Methode oder Eigenschaft je nach Laufzeittyp der Instanz, die sie aufruft, unterschiedliche Aktionen ausführen. Methoden oder Eigenschaften, die polymorph sind, werden als überschreibbar bezeichnet. Im Gegensatz dazu ist die Implementierung einer nicht überschreibbaren Methode oder Eigenschaft invariant; Die Implementierung ist identisch, unabhängig davon, ob die Methode oder Eigenschaft für eine Instanz der Klasse aufgerufen wird, in der sie deklariert wird, oder eine Instanz einer abgeleiteten Klasse. Wenn eine nicht überschreibbare Methode oder Eigenschaft aufgerufen wird, ist der Kompilierungszeittyp der Instanz der bestimmende Faktor. Beispiel:

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

Eine überschreibbare Methode kann auch sein MustOverride, was bedeutet, dass sie keinen Methodentext bereitstellt und überschrieben werden muss. MustOverride Methoden sind nur in MustInherit Klassen zulässig.

Im folgenden Beispiel definiert die Klasse Shape den abstrakten Begriff eines geometrischen Formobjekts, das sich selbst zeichnen kann:

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

Die Paint Methode liegt MustOverride daran, dass keine sinnvolle Standardimplementierung vorhanden ist. Die Ellipse- und Box-Klassen sind konkrete Shape-Implementierungen. Da diese Klassen nicht MustInheritvorhanden sind, müssen sie die Paint Methode außer Kraft setzen und eine tatsächliche Implementierung bereitstellen.

Es handelt sich um einen Fehler für einen Basiszugriff, um auf eine MustOverride Methode zu verweisen, wie im folgenden Beispiel veranschaulicht wird:

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

Für den MyBase.F() Aufruf wird ein Fehler gemeldet, da er auf eine MustOverride Methode verweist.

Außerkraftsetzungsmethoden

Ein Typ kann eine geerbte überschreibbare Methode außer Kraft setzen , indem eine Methode mit demselben Namen und , der Signatur und dem Markieren der Deklaration mit dem Overrides Modifizierer deklariert wird. Es gibt zusätzliche Anforderungen an überschreibende Methoden, die unten aufgeführt sind. Während eine Overridable Methodendeklaration eine neue Methode einführt, ersetzt eine Overrides Methodendeklaration die geerbte Implementierung der Methode.

Eine überschreibende Methode kann deklariert NotOverridablewerden, wodurch eine weitere Außerkraftsetzung der Methode in abgeleiteten Typen verhindert wird. In Der Tat NotOverridable werden Methoden in allen weiteren abgeleiteten Klassen nicht außer Kraft gesetzt.

Betrachten Sie das folgenden Beispiel:

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

Im Beispiel stellt die Klasse B zwei Overrides Methoden bereit: eine Methode F mit dem NotOverridable Modifizierer und einer Methode G , die nicht verwendet wird. Die Verwendung des NotOverridable Modifizierers verhindert, dass die Klasse C die methode weiter überschreibt F.

Eine überschreibende Methode kann auch deklariert MustOverridewerden, auch wenn die Methode, die sie überschreibt, nicht deklariert MustOverridewird. Dies erfordert, dass die enthaltende Klasse deklariert MustInherit wird und dass alle weiteren abgeleiteten Klassen, die nicht deklariert MustInherit werden, die Methode überschreiben müssen. Beispiel:

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

Im Beispiel überschreibt A.F die Klasse B eine MustOverride Methode. Dies bedeutet, dass alle von ihnen abgeleiteten Klassen außer FKraft B setzen müssen, es sei denn, sie werden ebenfalls deklariertMustInherit.

Ein Kompilierungszeitfehler tritt auf, es sei denn, alle folgenden Werte gelten für eine überschreibende Methode:

  • Der Deklarationskontext enthält eine einzelne geerbte Methode mit derselben Signatur und einem Rückgabetyp (falls vorhanden) wie die überschreibende Methode.
  • Die geerbte Methode, die überschrieben wird, kann überschrieben werden. Mit anderen Worten, die geerbte Methode, die überschrieben wird, ist nicht Shared oder NotOverridable.
  • Die Barrierefreiheitsdomäne der deklarierten Methode entspricht der Barrierefreiheitsdomäne der geerbten Methode, die überschrieben wird. Es gibt eine Ausnahme: Eine Protected Friend Methode muss von einer Protected Methode überschrieben werden, wenn sich die andere Methode in einer anderen Assembly befindet, auf die die überschreibende Methode keinen Zugriff hat Friend .
  • Die Parameter der Außerkraftsetzungsmethode stimmen mit den Parametern der überschriebenen Methode in Bezug auf die Verwendung der ByValModifizierer ByRefParamArray, und Optional Modifizierer überein, einschließlich der für optionale Parameter bereitgestellten Werte.
  • Die Typparameter der Überschreibungsmethode stimmen mit den Typparametern der überschriebenen Methode in Bezug auf Typeinschränkungen überein.

Beim Überschreiben einer Methode in einem generischen Basistyp muss die Überschreibungsmethode die Typargumente angeben, die den Basistypparametern entsprechen. Beispiel:

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

Beachten Sie, dass eine überschreibbare Methode in einer generischen Klasse für einige Typenargumente möglicherweise nicht überschrieben werden kann. Wenn die Methode deklariert MustOverridewird, bedeutet dies, dass einige Vererbungsketten möglicherweise nicht möglich sind. Beispiel:

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

Eine Außerkraftsetzungsdeklaration kann auf die überschriebene Basismethode mithilfe eines Basiszugriffs zugreifen, wie im folgenden Beispiel gezeigt:

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

Im Beispiel ruft der Aufruf in MyBase.PrintVariables() der Klasse Derived die PrintVariables in der Klasse Basedeklarierte Methode auf. Ein Basiszugriff deaktiviert den überschreibbaren Aufrufmechanismus und behandelt die Basismethode einfach als nicht überschreibbare Methode. Wenn der Aufruf Derived geschrieben CType(Me, Base).PrintVariables()wurde, würde sie rekursiv die PrintVariables in Deriveddeklarierte Methode aufrufen, nicht die in Base.

Nur wenn sie einen Overrides Modifizierer enthält, kann eine Methode eine andere Methode außer Kraft setzen. In allen anderen Fällen schattiert eine Methode mit derselben Signatur wie eine geerbte Methode einfach die geerbte Methode, wie im folgenden Beispiel gezeigt:

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

Im Beispiel enthält die Methode F in der Klasse Derived keinen Modifizierer und überschreibt daher die Methode F nicht in der KlasseBase.Overrides Stattdessen wird die Methode F in klassenschatten Derived , Baseund eine Warnung wird gemeldet, da die Deklaration keinen Modifizierer enthält ShadowsOverloads .

Im folgenden Beispiel schattiert die Methode F in Klassen Derived die überschreibbare Methode F , die von der Klasse Basegeerbt wurde:

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

Da die neue Methode F in der Klasse Derived Zugriff hat Private , umfasst der Bereich nur den Klassentext und Derived erweitert nicht auf die Klasse MoreDerived. Die Deklaration der Methode F in der Klasse MoreDerived darf daher die von der Klasse Basegeerbte Methode F überschreiben.

Wenn eine Overridable Methode aufgerufen wird, wird die abgeleitete Implementierung der Instanzmethode basierend auf dem Typ der Instanz aufgerufen, unabhängig davon, ob der Aufruf der Methode in der Basisklasse oder der abgeleiteten Klasse erfolgt. Die am häufigsten abgeleitete Implementierung einer Overridable Methode M in Bezug auf eine Klasse R wird wie folgt bestimmt:

  • Wenn R die Einführungserklärung OverridableMenthalten ist, ist dies die abgeleitetste Implementierung von M.

  • Andernfalls, wenn R eine Außerkraftsetzung von Menthält, ist dies die abgeleitetste Implementierung von M.

  • Andernfalls entspricht die am meisten abgeleitete Implementierung M der direkten Basisklasse von R.

Zugänglichkeit

Eine Deklaration gibt die Barrierefreiheit der entität an, die sie deklariert. Die Barrierefreiheit einer Entität ändert nicht den Bereich des Namens einer Entität. Die Barrierefreiheitsdomäne einer Deklaration ist der Satz aller Deklarationsplätze, in denen auf die deklarierte Entität zugegriffen werden kann.

Die fünf Zugriffstypen sind Public: , Protected, Friend, Protected Friendund Private. Public ist der zulässigste Zugriffstyp, und die vier anderen Typen sind alle Teilmengen von Public. Der am wenigsten zulässige Zugriffstyp ist Private, und die vier anderen Zugriffstypen sind alle Übersätze von Private.

AccessModifier
    : 'Public'
    | 'Protected'
    | 'Friend'
    | 'Private'
    | 'Protected' 'Friend'
    ;

Der Zugriffstyp für eine Deklaration wird über einen optionalen Zugriffsmodifizierer angegeben, der eine FriendPrivateProtectedPublicKombination von Protected und und .Friend Wenn kein Zugriffsmodifizierer angegeben ist, hängt der Standardzugriffstyp vom Deklarationskontext ab. die zulässigen Zugriffstypen hängen auch vom Deklarationskontext ab.

  • Entitäten, die mit dem Public Modifizierer deklariert sind, haben Public Zugriff. Es gibt keine Einschränkungen für die Verwendung von Public Entitäten.

  • Entitäten, die mit dem Protected Modifizierer deklariert sind, haben Protected Zugriff. Protected Der Zugriff kann nur für Member von Klassen (sowohl reguläre Typenmber als auch geschachtelte Klassen) oder auf Overridable Membern von Standardmodulen und Strukturen (die per Definition geerbt System.Object werden müssen) System.ValueTypeangegeben werden. Auf ein Protected Element kann auf eine abgeleitete Klasse zugegriffen werden, vorausgesetzt, das Element ist kein Instanzmemm oder der Zugriff erfolgt über eine Instanz der abgeleiteten Klasse. Protected Der Zugriff ist keine Obermenge des Friend Zugriffs.

  • Entitäten, die mit dem Friend Modifizierer deklariert sind, haben Friend Zugriff. Auf eine Entität mit Friend Zugriff kann nur innerhalb des Programms zugegriffen werden, das die Entitätsdeklaration oder Assemblys enthält, die zugriff über das System.Runtime.CompilerServices.InternalsVisibleToAttribute Attribut erhalten Friend haben.

  • Entitäten, die mit den Protected Friend Modifizierern deklariert sind, haben die Vereinigung und ProtectedFriend den Zugriff.

  • Entitäten, die mit dem Private Modifizierer deklariert sind, haben Private Zugriff. Auf eine Private Entität kann nur innerhalb des Deklarationskontexts zugegriffen werden, einschließlich aller geschachtelten Entitäten.

Die Barrierefreiheit in einer Deklaration hängt nicht von der Barrierefreiheit des Deklarationskontexts ab. Ein mit Zugriff deklarierter Private Typ kann z. B. ein Typmemm mit Public Zugriff enthalten.

Der folgende Code veranschaulicht verschiedene Barrierefreiheitsdomänen:

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

Die Klassen und Member in diesem Beispiel weisen die folgenden Barrierefreiheitsdomänen auf:

  • Die Barrierefreiheitsdomäne von A und A.X ist unbegrenzt.

  • Die Barrierefreiheitsdomäne von A.Y, B, B.X, B.CB.Y, B.C.Xund B.C.Y ist das enthaltende Programm.

  • Die Barrierefreiheitsdomäne von A.ZA.

  • Die Barrierefreiheitsdomäne von B.Z, B.D, B.D.Xund B.D.Y ist B, einschließlich B.C und B.D.

  • Die Barrierefreiheitsdomäne lautet B.C.ZB.C.

  • Die Barrierefreiheitsdomäne lautet B.D.ZB.D.

Wie das Beispiel zeigt, ist die Barrierefreiheitsdomäne eines Elements nie größer als die eines enthaltenden Typs. Auch wenn alle X Mitglieder Barrierefreiheit deklariert haben Public , verfügen alle Aber A.X über Barrierefreiheitsdomänen, die durch einen enthaltenden Typ eingeschränkt sind.

Der Zugriff auf Protected Instanzmber muss über eine Instanz des abgeleiteten Typs erfolgen, sodass nicht verknüpfte Typen keinen Zugriff auf die geschützten Member des anderen erhalten können. Beispiel:

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

Im obigen Beispiel hat die Klasse Guest nur Zugriff auf das geschützte Password Feld, wenn sie mit einer Instanz von Guest. Dadurch wird verhindert Guest , dass sie zugriff auf das Password Feld eines Employee Objekts erhält, indem Sie es einfach in Userdas Objekt umwandeln.

Für die Zwecke des Memberzugriffs Protected in generischen Typen enthält der Deklarationskontext Typparameter. Dies bedeutet, dass ein abgeleiteter Typ mit einem Satz von Typargumenten keinen Zugriff auf die Protected Member eines abgeleiteten Typs mit einem anderen Satz von Typargumenten hat. Beispiel:

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

Hinweis: Die C#-Sprache (und möglicherweise andere Sprachen) ermöglicht es einem generischen Typ, unabhängig davon, welche Typargumente bereitgestellt werden, auf Member zuzugreifen Protected . Dies sollte beim Entwerfen generischer Klassen Protected mit Membern beachtet werden.

Bestandteiltypen

Die Bestandteiltypen einer Deklaration sind die Typen, auf die durch die Deklaration verwiesen wird. Der Typ einer Konstante, der Rückgabetyp einer Methode und die Parametertypen eines Konstruktors sind z. B. alle Komponententypen. Die Barrierefreiheitsdomäne eines Bestandteiltyps einer Deklaration muss mit der oder einer Obermenge der Barrierefreiheitsdomäne der Deklaration selbst übereinstimmen. Beispiel:

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

Typ- und Namespacenamen

Viele Sprachkonstrukte erfordern einen Namespace oder Typ, der angegeben werden muss; diese können mithilfe einer qualifizierten Form des Namespaces oder des Namens des Typs angegeben werden. Ein qualifizierter Name besteht aus einer Reihe von Bezeichnern, die durch Punkte getrennt sind; Der Bezeichner auf der rechten Seite eines Punkts wird im Deklarationsbereich aufgelöst, der durch den Bezeichner auf der linken Seite des Punkts angegeben wird.

Der vollqualifizierte Name eines Namespaces oder Typs ist ein qualifizierter Name, der den Namen aller enthaltenden Namespaces und Typen enthält. Mit anderen Worten, der vollqualifizierte Name eines Namespaces oder Typs lautet N.T, wobei T der Name der Entität und N der vollqualifizierte Name seiner enthaltenden Entität ist.

Das folgende Beispiel zeigt mehrere Namespace- und Typdeklarationen zusammen mit den zugehörigen vollqualifizierten Namen in Zeilenkommentaren.

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

Beachten Sie, dass der Namespace X.Y an zwei verschiedenen Stellen im Quellcode deklariert wurde, aber diese beiden Teildeklarationen stellen nur einen einzigen Namespace namens X.Y dar, der sowohl die Klasse D als auch die Klasse E enthält.

In einigen Fällen kann ein qualifizierter Name mit dem Schlüsselwort Globalbeginnen. Das Schlüsselwort stellt den unbenannten äußersten Namespace dar, der in Situationen nützlich ist, in denen eine Deklaration einen umschließenden Namespace schattiert. Das Global Schlüsselwort ermöglicht in dieser Situation das Ausfangen von "escaping" in den äußersten Namespace. Beispiel:

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

Im obigen Beispiel ist der erste Methodenaufruf ungültig, da der Bezeichner System an die Klasse Systemund nicht an den Namespace Systemgebunden ist. Die einzige Möglichkeit für den Zugriff auf den System Namespace besteht darin, das Escapezeichen für den äußersten Namespace zu verwenden Global . Global kann nicht in einer Imports Anweisung oder Namespace Deklaration verwendet werden.

Da in anderen Sprachen Typen und Namespaces eingeführt werden können, die Schlüsselwörter in der Sprache entsprechen, erkennt Visual Basic Schlüsselwörter als Teil eines qualifizierten Namens, solange sie einem Punkt folgen. Schlüsselwörter, die auf diese Weise verwendet werden, werden als Bezeichner behandelt. Der qualifizierte Bezeichner ist z. B. ein gültiger qualifizierter Bezeichner X.Default.Class , aber Default.Class nicht.

Qualifizierte Namensauflösung für Namespaces und Typen

Angesichts eines qualifizierten Namespaces oder Typnamens des Formulars N.R(Of A), wobei R es sich um den am weitesten rechts sten Bezeichner im qualifizierten Namen handelt und A eine optionale Typargumentliste ist, beschreiben die folgenden Schritte, wie sie bestimmen, auf welchen Namespace oder Typ der qualifizierte Name verweist:

  1. Lösen Sie Ndie Regeln für qualifizierte oder nicht qualifizierte Namensauflösung auf.

  2. Wenn die Auflösung eines N Fehlers auftritt oder in einen Typparameter aufgelöst wird, tritt ein Kompilierungszeitfehler auf.

  3. Andernfalls, wenn R der Name eines Namespaces in N übereinstimmt und keine Typargumente angegeben wurden, oder R entspricht ein barrierefreier Typ N mit der gleichen Anzahl von Typparametern wie Typargumente, falls vorhanden, bezieht sich der qualifizierte Name auf diesen Namespace oder Typ.

  4. Andernfalls entspricht der N Name eines oder mehrerer Standardmodule dem R Namen eines barrierefreien Typs mit der gleichen Anzahl von Typparametern wie Typargumente, falls vorhanden, in genau einem Standardmodul, dann bezieht sich der qualifizierte Name auf diesen Typ. Wenn R der Name von barrierefreien Typen mit der gleichen Anzahl von Typparametern wie Typargumente übereinstimmt, tritt in mehr als einem Standardmodul ein Kompilierungszeitfehler auf.

  5. Andernfalls tritt ein Kompilierfehler auf.

Hinweis: Eine Auswirkung dieses Lösungsprozesses besteht darin, dass Typmmber keine Schattennamespaces oder Typen beim Auflösen von Namespace- oder Typnamen verwenden.

Nicht qualifizierte Namensauflösung für Namespaces und Typen

Bei einem nicht qualifizierten Namen R(Of A), wobei A es sich um eine optionale Typargumentliste handelt, beschreiben die folgenden Schritte, wie Sie bestimmen, auf welchen Namespace oder Typ der nicht qualifizierte Name verweist:

  1. Wenn R mit dem Namen eines Typparameters der aktuellen Methode übereinstimmt und keine Typargumente angegeben wurden, verweist der nicht qualifizierte Name auf diesen Typparameter.

  2. Für jeden geschachtelten Typ, der den Namensverweis enthält, beginnend mit dem innersten Typ und zum äußersten Rand:

    1. Wenn R der Name eines Typparameters im aktuellen Typ übereinstimmt und keine Typargumente angegeben wurden, verweist der nicht qualifizierte Name auf diesen Typparameter.
    2. Andernfalls entspricht der R Name eines geschachtelten Typs mit derselben Anzahl von Typparametern wie Typargumente, falls vorhanden, der nicht qualifizierte Name verweist auf diesen Typ.
  3. Für jeden geschachtelten Namespace, der den Namensverweis enthält, beginnend mit dem innersten Namespace und dem äußersten Namespace:

    1. Wenn R der Name eines geschachtelten Namespaces im aktuellen Namespace entspricht und keine Typargumentliste angegeben wird, bezieht sich der nicht qualifizierte Name auf diesen geschachtelten Namespace.
    2. Andernfalls entspricht der R Name eines barrierefreien Typs mit der gleichen Anzahl von Typparametern wie Typargumente( falls vorhanden) im aktuellen Namespace, dann bezieht sich der nicht qualifizierte Name auf diesen Typ.
    3. Wenn der Namespace ein oder mehrere barrierefreie Standardmodule enthält und R dem Namen eines verschachtelten Typs mit derselben Anzahl von Typparametern entspricht, wie Typargumente, falls vorhanden, in genau einem Standardmodul bezieht sich der nicht qualifizierte Name auf diesen geschachtelten Typ. Wenn R der Name von geschachtelten Typen mit der gleichen Anzahl von Typparametern wie Typargumente übereinstimmt, tritt in mehr als einem Standardmodul ein Kompilierungsfehler auf.
  4. Wenn die Quelldatei einen oder mehrere Importalias enthält und R dem Namen eines dieser Aliase entspricht, bezieht sich der nicht qualifizierte Name auf diesen Importalias. Wenn eine Typargumentliste angegeben wird, tritt ein Kompilierungszeitfehler auf.

  5. Wenn die Quelldatei, die den Namensverweis enthält, einen oder mehrere Importe hat:

    1. Wenn R der Name eines barrierefreien Typs mit der gleichen Anzahl von Typparametern wie Typargumente übereinstimmt, bezieht sich der nicht qualifizierte Name auf diesen Typ, falls vorhanden, in genau einem Import. Wenn R der Name eines barrierefreien Typs mit der gleichen Anzahl von Typparametern wie Typargumente übereinstimmt, tritt bei mehreren Importen und alle nicht demselben Typ ein Kompilierungszeitfehler auf.
    2. Andernfalls, wenn keine Typargumentliste angegeben wurde und R dem Namen eines Namespaces mit barrierefreien Typen in genau einem Import entspricht, bezieht sich der nicht qualifizierte Name auf diesen Namespace. Wenn keine Typargumentliste angegeben wurde und R dem Namen eines Namespaces mit barrierefreien Typen in mehr als einem Import entspricht und alle nicht derselbe Namespace sind, tritt ein Kompilierungszeitfehler auf.
    3. Andernfalls, wenn die Importe ein oder mehrere barrierefreie Standardmodule enthalten und R dem Namen eines verschachtelten Typs mit derselben Anzahl von Typparametern entsprechen wie Typargumente, falls vorhanden, in genau einem Standardmodul, bezieht sich der nicht qualifizierte Name auf diesen Typ. Wenn R der Name von geschachtelten Typen mit der gleichen Anzahl von Typparametern wie Typargumente übereinstimmt, tritt in mehr als einem Standardmodul ein Kompilierungsfehler auf.
  6. Wenn die Kompilierungsumgebung einen oder mehrere Importalias definiert und R dem Namen eines dieser Aliase entspricht, bezieht sich der nicht qualifizierte Name auf diesen Importalias. Wenn eine Typargumentliste angegeben wird, tritt ein Kompilierungszeitfehler auf.

  7. Wenn die Kompilierungsumgebung einen oder mehrere Importe definiert:

    1. Wenn R der Name eines barrierefreien Typs mit der gleichen Anzahl von Typparametern wie Typargumente übereinstimmt, bezieht sich der nicht qualifizierte Name auf diesen Typ, falls vorhanden, in genau einem Import. Wenn R der Name eines barrierefreien Typs mit der gleichen Anzahl von Typparametern wie Typargumente übereinstimmt, tritt bei mehr als einem Import ein Kompilierungszeitfehler auf.
    2. Andernfalls, wenn keine Typargumentliste angegeben wurde und R dem Namen eines Namespaces mit barrierefreien Typen in genau einem Import entspricht, bezieht sich der nicht qualifizierte Name auf diesen Namespace. Wenn keine Typargumentliste angegeben wurde und R dem Namen eines Namespaces mit barrierefreien Typen in mehr als einem Import entspricht, tritt ein Kompilierungszeitfehler auf.
    3. Andernfalls, wenn die Importe ein oder mehrere barrierefreie Standardmodule enthalten und R dem Namen eines verschachtelten Typs mit derselben Anzahl von Typparametern entsprechen wie Typargumente, falls vorhanden, in genau einem Standardmodul, bezieht sich der nicht qualifizierte Name auf diesen Typ. Wenn R der Name von geschachtelten Typen mit der gleichen Anzahl von Typparametern wie Typargumente übereinstimmt, tritt in mehr als einem Standardmodul ein Kompilierungsfehler auf.
  8. Andernfalls tritt ein Kompilierfehler auf.

Hinweis: Eine Auswirkung dieses Lösungsprozesses besteht darin, dass Typmmber keine Schattennamespaces oder Typen beim Auflösen von Namespace- oder Typnamen verwenden.

Normalerweise kann ein Name nur einmal in einem bestimmten Namespace vorkommen. Da Namespaces jedoch über mehrere .NET-Assemblys deklariert werden können, ist es möglich, eine Situation zu haben, in der zwei Assemblys einen Typ mit demselben vollqualifizierten Namen definieren. In diesem Fall wird ein im aktuellen Satz von Quelldateien deklarierter Typ gegenüber einem Typ bevorzugt, der in einer externen .NET-Assembly deklariert ist. Andernfalls ist der Name mehrdeutig und es gibt keine Möglichkeit, den Namen zu disambiguieren.

Variablen

Eine Variable stellt einen Speicherort dar. Jede Variable hat einen Typ, der bestimmt, welche Werte in der Variable gespeichert werden können. Da Visual Basic eine typsichere Sprache ist, verfügt jede Variable in einem Programm über einen Typ, und die Sprache garantiert, dass in Variablen gespeicherte Werte immer vom geeigneten Typ sind. Variablen werden immer auf den Standardwert ihres Typs initialisiert, bevor ein Verweis auf die Variable vorgenommen werden kann. Es ist nicht möglich, auf nicht initialisierte Arbeitsspeicher zuzugreifen.

Generische Typen und Methoden

Typen (mit Ausnahme von Standardmodulen und Enumerationstypen) und Methoden können Typparameter deklarieren, bei denen es sich um Typen handelt, die erst bereitgestellt werden, wenn eine Instanz des Typs deklariert oder die Methode aufgerufen wird. Typen und Methoden mit Typparametern werden auch als generische Typen und generische Methoden bezeichnet, da der Typ oder die Methode generisch geschrieben werden muss, ohne spezifische Kenntnisse der Typen, die von Code bereitgestellt werden, der den Typ oder die Methode verwendet.

Hinweis: Zu diesem Zeitpunkt können Methoden und Stellvertretungen generisch, Eigenschaften, Ereignisse und Operatoren selbst nicht generisch sein. Sie können jedoch Typparameter aus der enthaltenden Klasse verwenden.

Aus Sicht des generischen Typs oder der Methode ist ein Typparameter ein Platzhaltertyp, der bei Verwendung des Typs oder der Methode mit einem tatsächlichen Typ gefüllt wird. Typargumente werden durch die Typparameter im Typ oder die Methode an dem Punkt ersetzt, an dem der Typ oder die Methode verwendet wird. Eine generische Stapelklasse könnte beispielsweise wie:

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

Deklarationen, die die Stack(Of ItemType) Klasse verwenden, müssen ein Typargument für den Typparameter ItemTypeangeben. Dieser Typ wird dann überall ItemType in der Klasse ausgefüllt:

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

Typparameter

Typparameter können für Typ- oder Methodendeklarationen angegeben werden. Jeder Typparameter ist ein Bezeichner, der ein Platzhalter für ein Typargument ist, das zum Erstellen eines konstruierten Typs oder einer konstruierten Methode bereitgestellt wird. Im Gegensatz dazu ist ein Typargument der tatsächliche Typ, der für den Typparameter ersetzt wird, wenn ein generischer Typ oder eine generische Methode verwendet wird.

TypeParameterList
    : OpenParenthesis 'Of' TypeParameter ( Comma TypeParameter )* CloseParenthesis
    ;

TypeParameter
    : VarianceModifier? Identifier TypeParameterConstraints?
    ;

VarianceModifier
    : 'In' | 'Out'
    ;

Jeder Typparameter in einer Typ- oder Methodendeklaration definiert einen Namen im Deklarationsbereich dieses Typs oder dieser Methode. Daher kann er nicht denselben Namen wie ein anderer Typparameter, ein Typmemm, einen Methodenparameter oder eine lokale Variable aufweisen. Der Bereich eines Typparameters für einen Typ oder eine Methode ist der gesamte Typ oder die gesamte Methode. Da Typparameter auf die gesamte Typdeklaration festgelegt sind, können geschachtelte Typen äußere Typparameter verwenden. Dies bedeutet auch, dass Typenparameter immer angegeben werden müssen, wenn auf Typen zugegriffen wird, die in generischen Typen geschachtelt sind:

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

Im Gegensatz zu anderen Membern einer Klasse werden Typparameter nicht geerbt. Typparameter in einem Typ können nur mit ihrem einfachen Namen referenziert werden; Mit anderen Worten, sie können nicht mit dem enthaltenden Typnamen qualifiziert werden. Obwohl es sich um einen schlechten Programmierstil handelt, können die Typparameter in einem geschachtelten Typ einen Element- oder Typparameter ausblenden, der im äußeren Typ deklariert ist:

Class Outer(Of T)
    Class Inner(Of T)
        Public t1 As T    ' Refers to Inner's T
    End Class
End Class

Typen und Methoden können basierend auf der Anzahl der Typparameter (oder Desarität) überladen werden, die von den Typen oder Methoden deklariert werden. Die folgenden Erklärungen sind z. B. legal:

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

Bei Typen werden Überladungen immer mit der Anzahl der angegebenen Typargumente abgeglichen. Dies ist nützlich, wenn sie sowohl generische als auch nicht generische Klassen zusammen im selben Programm verwenden:

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

Regeln für Methoden, die für Typparameter überladen sind, werden im Abschnitt zur Methodenüberladungsauflösung behandelt.

In der enthaltenden Deklaration werden Typparameter als vollständige Typen betrachtet. Da ein Typparameter mit vielen verschiedenen tatsächlichen Typargumenten instanziiert werden kann, weisen Typparameter geringfügig unterschiedliche Vorgänge und Einschränkungen als andere Typen auf, wie unten beschrieben:

  • Ein Typparameter kann nicht direkt verwendet werden, um eine Basisklasse oder Schnittstelle zu deklarieren.

  • Die Regeln für die Elementsuche nach Typparametern hängen ggf. von den Einschränkungen ab, die auf den Typparameter angewendet werden.

  • Die verfügbaren Konvertierungen für einen Typparameter hängen ggf. von den Einschränkungen ab, die auf die Typparameter angewendet werden.

  • Wenn keine Structure Einschränkung vorhanden ist, kann ein Wert mit einem Typ, der durch einen Typparameter dargestellt wird, mit Nothing "using Is " und IsNot".

  • Ein Typparameter kann nur in einem New Ausdruck verwendet werden, wenn der Typparameter durch eine New oder eine Structure Einschränkung eingeschränkt wird.

  • Ein Typparameter kann nicht an einer beliebigen Stelle innerhalb einer Attributausnahme innerhalb eines Ausdrucks GetType verwendet werden.

  • Typparameter können als Typargumente für andere generische Typen und Parameter verwendet werden.

Das folgende Beispiel ist ein generischer Typ, der die Stack(Of ItemType) Klasse erweitert:

Class MyStack(Of ItemType)
    Inherits Stack(Of ItemType)

    Public ReadOnly Property Size() As Integer
        Get
            Return CurrentIndex
        End Get
    End Property
End Class

Wenn eine Deklaration ein Typargument angibt MyStack, wird auch das gleiche Typargument angewendet Stack .

Als Typ sind Typparameter rein ein Kompilierungszeitkonstrukt. Zur Laufzeit ist jeder Typparameter an einen Laufzeittyp gebunden, der durch Angeben eines Typarguments an die generische Deklaration angegeben wurde. Daher ist der Typ einer variablen, die mit einem Typparameter deklariert wird, zur Laufzeit ein nicht generischer Typ oder ein bestimmter konstruierter Typ. Die Laufzeitausführung aller Anweisungen und Ausdrücke mit Typparametern verwendet den tatsächlichen Typ, der als Typargument für diesen Parameter angegeben wurde.

Typeinschränkungen

Da ein Typargument ein beliebiger Typ im Typsystem sein kann, kann ein generischer Typ oder eine Methode keine Annahmen zu einem Typparameter machen. Daher werden die Member eines Typparameters als Member des Typs Objectbetrachtet, da alle Typen von Object.

Im Fall einer Sammlung wie Stack(Of ItemType), kann diese Tatsache keine besonders wichtige Einschränkung sein, aber es kann Fälle geben, in denen ein generischer Typ eine Annahme über die Typen vornehmen möchte, die als Typargumente angegeben werden. Typeinschränkungen können für Typparameter festgelegt werden, die einschränken, welche Typen als Typparameter bereitgestellt werden können, und generische Typen oder Methoden erlauben, mehr über Typparameter zu übernehmen.

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 diesem Beispiel schränkt der DisposableStack(Of ItemType) Typparameter nur typen ein, die die Schnittstelle System.IDisposableimplementieren. Daher kann sie eine Dispose Methode implementieren, die alle Objekte entfernt, die noch in der Warteschlange verbleiben.

Eine Typeinschränkung muss eine der besonderen Einschränkungen Classsein, Structureoder Newes muss ein Typ sein, in T dem:

  • T ist eine Klasse, eine Schnittstelle oder ein Typparameter.

  • T ist nicht NotInheritable.

  • T ist kein Typ oder ein Typ, der von einem der folgenden speziellen Typen geerbt wird: System.Array, , , System.Delegate, System.MulticastDelegate, , System.Enumoder System.ValueType.

  • T ist nicht Object. Da alle Typen von Objectdieser Einschränkung abgeleitet sind, hätte eine solche Einschränkung keine Auswirkung, wenn sie zulässig wäre.

  • T muss mindestens so barrierefrei sein wie der generische Typ oder die deklarierte Methode.

Mehrere Typeinschränkungen können für einen einzelnen Typparameter angegeben werden, indem die Typeinschränkungen in geschweifte Klammern ({}).) eingeschlossen werden. Nur eine Typeinschränkung für einen bestimmten Typparameter kann eine Klasse sein. Es ist ein Fehler, eine Structure spezielle Einschränkung mit einer benannten Klasseneinschränkung oder der Class speziellen Einschränkung zu kombinieren.

Class ControlFactory(Of T As {Control, New})
    ...
End Class

Typeinschränkungen können die enthaltenden Typen oder einen der Typenparameter verwenden. Im folgenden Beispiel erfordert die Einschränkung, dass das angegebene Typargument eine generische Schnittstelle als Typargument implementiert:

Class Sorter(Of V As IComparable(Of V))
    ...
End Class

Die spezielle Typeinschränkung Class schränkt das angegebene Typargument auf einen beliebigen Bezugstyp ein.

Hinweis: Die spezielle Typeinschränkung Class kann von einer Schnittstelle erfüllt werden. Und eine Struktur kann eine Schnittstelle implementieren. Daher kann die Einschränkung (Of T As U, U As Class) mit "T" einer Struktur (die die Class besondere Einschränkung nicht erfüllt) zufrieden sein, und "U" eine Schnittstelle, die sie implementiert (was die Class spezielle Einschränkung erfüllt).

Die spezielle Typeinschränkung Structure schränkt das angegebene Typargument auf einen beliebigen Werttyp außer System.Nullable(Of T)ein.

Hinweis: Struktureinschränkungen lassen nicht zu System.Nullable(Of T) , sodass es nicht möglich ist, als Typargument an sich selbst zu liefern System.Nullable(Of T) .

Die spezielle Typeinschränkung New erfordert, dass das angegebene Typargument über einen barrierefreien parameterlosen Konstruktor verfügen muss und nicht deklariert MustInheritwerden kann. Beispiel:

Class Factory(Of T As New)
    Function CreateInstance() As T
        Return New T()
    End Function
End Class

Eine Klassentypeinschränkung erfordert, dass das angegebene Typargument entweder diesen Typ als oder erben muss. Eine Schnittstellentypeinschränkung erfordert, dass das angegebene Typargument diese Schnittstelle implementieren muss. Eine Typparametereinschränkung erfordert, dass das angegebene Typargument von allen Für den übereinstimmenden Typparameter angegebenen Grenzen abgeleitet oder implementiert werden muss. Beispiel:

Class List(Of T)
    Sub AddRange(Of S As T)(collection As IEnumerable(Of S))
        ...
    End Sub
End Class

In diesem Beispiel ist der Typparameter S auf AddRange den Typparameter T von List. Dies bedeutet, dass ein List(Of Control) Typparameter auf einen beliebigen Typ beschränkt AddRangewird, der von diesen erbt oder von Control.

Eine Typparametereinschränkung Of S As T wird durch transitives Hinzufügen aller T'Einschränkungen auf S' außer den speziellen Einschränkungen (Class, , StructureNew) aufgelöst. Es ist ein Fehler, Zirkeleinschränkungen (z. B. Of S As T, T As S) zu haben. Es ist ein Fehler, eine Typparametereinschränkung zu haben, die selbst über die Structure Einschränkung verfügt. Nach dem Hinzufügen von Einschränkungen ist es möglich, dass eine Reihe von besonderen Situationen auftreten kann:

  • Wenn mehrere Klasseneinschränkungen vorhanden sind, wird die am häufigsten abgeleitete Klasse als Einschränkung betrachtet. Wenn eine oder mehrere Klasseneinschränkungen keine Vererbungsbeziehung aufweisen, ist die Einschränkung nicht zufriedenstellend und ein Fehler.

  • Wenn ein Typparameter eine Structure spezielle Einschränkung mit einer benannten Klasseneinschränkung oder der Class speziellen Einschränkung kombiniert, handelt es sich um einen Fehler. Eine Klasseneinschränkung kann sein NotInheritable, in diesem Fall werden keine abgeleiteten Typen dieser Einschränkung akzeptiert, und es handelt sich um einen Fehler.

Der Typ kann einer von oder einem Typ sein, der von den folgenden speziellen Typen geerbt wird: System.Array, , , System.Delegate, System.MulticastDelegate, , System.Enumoder System.ValueType. In diesem Fall wird nur der Typ oder ein von ihr geerbter Typ akzeptiert. Ein Typparameter, der DirectCast auf einen dieser Typen beschränkt ist, kann nur die vom Operator zulässigen Konvertierungen verwenden. Beispiel:

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

Darüber hinaus kann ein Typparameter, der aufgrund einer der oben genannten Entspannungen auf einen Werttyp beschränkt ist, keine Methoden aufrufen, die für diesen Werttyp definiert sind. Beispiel:

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

Wenn die Einschränkung nach der Ersetzung als Arraytyp endet, ist auch jeder kovariante Arraytyp zulässig. Beispiel:

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

Ein Typparameter mit einer Klassen- oder Schnittstelleneinschränkung gilt als die gleichen Member wie diese Klasse oder Schnittstelleneinschränkung. Wenn ein Typparameter mehrere Einschränkungen aufweist, wird der Typparameter als Vereinigung aller Member der Einschränkungen betrachtet. Wenn Mitglieder mit demselben Namen in mehr als einer Einschränkung vorhanden sind, werden Member in der folgenden Reihenfolge ausgeblendet: Die Klasseneinschränkung blendet Elemente in Schnittstelleneinschränkungen aus, die Elemente System.ValueType ausblenden (wenn Structure Einschränkung angegeben ist), in denen Member Objectausgeblendet werden. Wenn ein Element mit demselben Namen in mehr als einer Schnittstelleneinschränkung angezeigt wird, ist das Element nicht verfügbar (wie in der Vererbung mehrerer Schnittstellen), und der Typparameter muss in die gewünschte Schnittstelle umwandeln. Beispiel:

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

Beim Angeben von Typparametern als Typargumente müssen die Typparameter die Einschränkungen der übereinstimmenden Typparameter erfüllen.

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

Werte eines eingeschränkten Typparameters können verwendet werden, um auf die Instanzmember zuzugreifen, einschließlich Instanzmethoden, die in der Einschränkung angegeben sind.

Interface IPrintable
    Sub Print()
End Interface

Class Printer(Of V As IPrintable)
    Sub PrintOne(v1 As V)
        V1.Print()
    End Sub
End Class

Typparameterabweichung

Ein Typparameter in einer Schnittstelle oder einer Delegattypdeklaration kann optional einen Varianzmodifizierer angeben. Typparameter mit Varianzmodifizierern beschränken, wie der Typparameter in der Schnittstelle oder dem Delegattyp verwendet werden kann, aber zulassen, dass eine generische Schnittstelle oder ein Stellvertretungstyp mit variantenkompatiblen Typargumenten in einen anderen generischen Typ konvertiert werden kann. Beispiel:

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

Generische Schnittstellen mit Typparametern mit Varianzmodifizierern haben mehrere Einschränkungen:

  • Sie können keine Ereignisdeklaration enthalten, die eine Parameterliste angibt (aber eine benutzerdefinierte Ereignisdeklaration oder eine Ereignisdeklaration mit einem Delegattyp ist zulässig).

  • Sie können keinen geschachtelten Klassen-, Struktur- oder Enumerationstyp enthalten.

Hinweis: Diese Einschränkungen sind darauf zurückzuführen, dass Typen, die in generischen Typen geschachtelt sind, implizit die generischen Parameter ihres übergeordneten Elements kopieren. Bei geschachtelten Klassen, Strukturen oder Aufzählungstypen können diese Typen keine Varianzmodifizierer für ihre Typparameter haben. Im Falle einer Ereignisdeklaration mit einer Parameterliste könnte die generierte geschachtelte Delegatklasse verwirrende Fehler haben, wenn ein Typ, der in einer In Position verwendet wird (d. h. ein Parametertyp), tatsächlich an einer Out Position verwendet wird (d. h. der Typ des Ereignisses).

Ein Typparameter, der mit dem Modifizierer "Out" deklariert wird, ist kovariant. Informell kann ein kovarianter Typparameter nur an einer Ausgabeposition verwendet werden – d. h. einen Wert, der von der Schnittstelle oder dem Delegattyp zurückgegeben wird - und kann nicht an einer Eingabeposition verwendet werden. Ein Typ T gilt als kovariant , wenn:

  • T ist ein Klassen-, Struktur- oder Enumerationstyp.

  • T ist nicht generischer Delegat- oder Schnittstellentyp.

  • T ist ein Arraytyp, dessen Elementtyp kovariant gültig ist.

  • T ist ein Typparameter, der nicht als Out Typparameter deklariert wurde.

  • T ist eine konstruierte Schnittstelle oder ein Delegattyp X(Of P1,...,Pn) mit Typargumenten A1,...,An , z. B.:

    • Wenn Pi er als Ausgabetypparameter deklariert wurde, Ai ist er kovariant.

    • Wenn Pi sie als In-Typ-Parameter deklariert wurde, Ai ist dies kontravariant.

Folgendes muss in einer Schnittstelle oder einem Delegattyp kovariant gültig sein:

  • Die Basisschnittstelle einer Schnittstelle.

  • Der Rückgabetyp einer Funktion oder des Delegattyps.

  • Der Typ einer Eigenschaft, wenn ein Get Accessor vorhanden ist.

  • Der Typ eines beliebigen ByRef Parameters.

Beispiel:

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

Hinweis: Out ist kein reserviertes Wort.

Ein Typparameter, der mit dem In-Modifizierer deklariert wird, ist kontravariant. Informell kann ein kontravarianter Typparameter nur an einer Eingabeposition verwendet werden – d. h. einen Wert, der an die Schnittstelle oder den Delegattyp übergeben wird - und kann nicht an einer Ausgabeposition verwendet werden. Ein Typ T gilt als gültig kontravariant , wenn:

  • T ist ein Klassen-, Struktur- oder Enumerationstyp.

  • T ist ein nicht generischer Delegat oder Schnittstellentyp.

  • T ist ein Arraytyp, dessen Elementtyp kontravariant gültig ist.

  • T ist ein Typparameter, der nicht als In-Typ-Parameter deklariert wurde.

  • T ist eine konstruierte Schnittstelle oder ein Delegattyp X(Of P1,...,Pn) mit Typargumenten A1,...,An , z. B.:

    • Wenn Pi sie als Out Typparameter deklariert wurde, Ai ist dies kontravariant.

    • Wenn Pi sie als In Typparameter deklariert wurde, Ai ist dies kovariant.

Folgendes muss in einer Schnittstelle oder einem Delegattyp kontravariant gültig sein:

  • Der Typ eines Parameters.

  • Eine Typeinschränkung für einen Methodentypparameter.

  • Der Typ einer Eigenschaft, wenn sie über einen Set Accessor verfügt.

  • Der Typ eines Ereignisses.

Beispiel:

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

In dem Fall, in dem ein Typ gültig sein muss, kontravariant und kovariant sein muss (z. B. eine Eigenschaft mit einem und Set einem Get Accessor oder einem ByRef Parameter), kann kein Variant-Typparameter verwendet werden.

Die Ko- und Kontraabweichung führen zu einem "Rautendeutigkeitsproblem". Beachten Sie den folgenden Code:

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()

Die Klasse C kann auf zwei Arten konvertiert IEnumerable(Of Object) werden, sowohl durch die kovariante Konvertierung von IEnumerable(Of String) und über die kovariante Konvertierung von IEnumerable(Of Exception). Die CLR gibt nicht an, von welchen der beiden Methoden aufgerufen c.GetEnumerator()wird. Im Allgemeinen gilt, wann immer eine Klasse deklariert wird, um eine kovariante Schnittstelle mit zwei verschiedenen generischen Argumenten zu implementieren, die einen gemeinsamen Supertyp haben (z. B. in diesem Fall String und Exception den gemeinsamen Supertyp Object), oder eine Klasse wird deklariert, um eine kontravariante Schnittstelle mit zwei verschiedenen generischen Argumenten zu implementieren, die einen gemeinsamen Untertyp aufweisen, dann kann die Mehrdeutigkeit auftreten. Der Compiler gibt eine Warnung für solche Deklarationen.