Freigeben über


Elemente eingeben

Typenmitglieder definieren Speicherorte und ausführbaren Code. Sie können Methoden, Konstruktoren, Ereignisse, Konstanten, Variablen und Eigenschaften sein.

Implementierung der Schnittstellenmethode

Methoden, Ereignisse und Eigenschaften können Schnittstellenmmber implementieren. Zum Implementieren eines Schnittstellenmembers gibt eine Memberdeklaration das Implements Schlüsselwort an und listet ein oder mehrere Schnittstellenmember auf.

ImplementsClause
    : ( 'Implements' ImplementsList )?
    ;

ImplementsList
    : InterfaceMemberSpecifier ( Comma InterfaceMemberSpecifier )*
    ;

InterfaceMemberSpecifier
    : NonArrayTypeName Period IdentifierOrKeyword
    ;

Methoden und Eigenschaften, die Schnittstellenmember implementieren, sind implizit NotOverridable , es sei denn, es wird deklariert MustOverride, Overridableoder ein anderes Element überschrieben. Es ist ein Fehler für ein Mitglied, das ein Schnittstellenmitglied implementiert.Shared Die Barrierefreiheit eines Mitglieds hat keine Auswirkungen auf die Möglichkeit, Schnittstellenmember zu implementieren.

Damit eine Schnittstellenimplementierung gültig ist, muss die Implementierenliste des enthaltenden Typs eine Schnittstelle benennen, die ein kompatibles Element enthält. Ein kompatibles Mitglied ist ein Mitglied, dessen Signatur der Signatur des implementierenden Mitglieds entspricht. Wenn eine generische Schnittstelle implementiert wird, wird das in der Implements-Klausel angegebene Typargument bei der Überprüfung der Kompatibilität in die Signatur ersetzt. Beispiel:

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

Class C1
    Implements I1(Of Integer)

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

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

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

Wenn ein Ereignis, das mit einem Delegatentyp deklariert wird, ein Schnittstellenereignis implementiert, ist ein kompatibles Ereignis ein Ereignis, dessen zugrunde liegender Delegattyp derselbe Typ ist. Andernfalls verwendet das Ereignis den Delegatentyp aus dem Schnittstellenereignis, das implementiert wird. Wenn ein solches Ereignis mehrere Schnittstellenereignisse implementiert, müssen alle Schnittstellenereignisse den gleichen zugrunde liegenden Delegatentyp aufweisen. Beispiel:

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

Class Button
    Implements ClickEvents

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

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

Class Label
    Implements ClickEvents

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

Ein Schnittstellenelement in der Implementierungsliste wird mithilfe eines Typnamens, eines Punkts und eines Bezeichners angegeben. Der Typname muss eine Schnittstelle in der Implementierungsliste oder eine Basisschnittstelle einer Schnittstelle in der Implementierungsliste sein, und der Bezeichner muss ein Element der angegebenen Schnittstelle sein. Ein einzelnes Mitglied kann mehrere übereinstimmende Schnittstellenmememm implementieren.

Interface ILeft
    Sub F()
End Interface

Interface IRight
    Sub F()
End Interface

Class Test
    Implements ILeft, IRight

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

Wenn das implementierte Schnittstellenmitglied aufgrund der Vererbung mehrerer Schnittstellen in allen explizit implementierten Schnittstellen nicht verfügbar ist, muss das implementierende Mitglied explizit auf eine Basisschnittstelle verweisen, auf der das Element verfügbar ist. Wenn sie beispielsweise ein Element Menthalten und I2I3 von und erben von I1 und I2, implementiert und I2.Mimplementiert I1.M ein Implementierungstyp I3 .I1 Wenn ein Schnittstellenschatten geerbte Member multiplizieren, muss ein Implementierungstyp die geerbten Member und die Member implementieren, die sie schattieren.

Interface ILeft
    Sub F()
End Interface

Interface IRight
    Sub F()
End Interface

Interface ILeftRight
    Inherits ILeft, IRight

    Shadows Sub F()
End Interface

Class Test
    Implements ILeftRight

    Sub LeftF() Implements ILeft.F
    End Sub

    Sub RightF() Implements IRight.F
    End Sub

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

Wenn die enthaltende Schnittstelle des Schnittstellenelements generisch ist, müssen dieselben Typargumente wie die implementierte Schnittstelle angegeben werden. Beispiel:

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

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

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

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

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

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

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

Methodik

Methoden enthalten die ausführbaren Anweisungen eines Programms.

MethodMemberDeclaration
    : MethodDeclaration
    | ExternalMethodDeclaration
    ;

InterfaceMethodMemberDeclaration
    : InterfaceMethodDeclaration
    ;

MethodDeclaration
    : SubDeclaration
    | MustOverrideSubDeclaration
    | FunctionDeclaration
    | MustOverrideFunctionDeclaration
    ;

InterfaceMethodDeclaration
    : InterfaceSubDeclaration
    | InterfaceFunctionDeclaration
    ;

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

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

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

MustOverrideSubDeclaration
    : Attributes? MustOverrideProcedureModifier+ SubSignature
      HandlesOrImplements? StatementTerminator
    ;

InterfaceSubDeclaration
    : Attributes? InterfaceProcedureModifier* SubSignature StatementTerminator
    ;

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

MustOverrideFunctionDeclaration
    : Attributes? MustOverrideProcedureModifier+ FunctionSignature
      HandlesOrImplements? StatementTerminator
    ;

InterfaceFunctionDeclaration
    : Attributes? InterfaceProcedureModifier* FunctionSignature StatementTerminator
    ;

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

MustOverrideProcedureModifier
    : ProcedureModifier
    | 'MustOverride'
    ;

InterfaceProcedureModifier
    : 'Shadows' | 'Overloads'
    ;

HandlesOrImplements
    : HandlesClause
    | ImplementsClause
    ;

Methoden, die über eine optionale Liste von Parametern und einen optionalen Rückgabewert verfügen, werden entweder freigegeben oder nicht freigegeben. Auf freigegebene Methoden wird über die Klasse oder Instanzen der Klasse zugegriffen. Auf nicht freigegebene Methoden, auch als Instanzmethoden bezeichnet, wird über Instanzen der Klasse zugegriffen. Das folgende Beispiel zeigt eine Klasse Stack mit mehreren gemeinsam genutzten Methoden (Clone und Flip) sowie mehrere Instanzmethoden (Push, Popund ToString):

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

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

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

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

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

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

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

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

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

Methoden können überladen werden, was bedeutet, dass mehrere Methoden möglicherweise denselben Namen haben, solange sie eindeutige Signaturen haben. Die Signatur einer Methode besteht aus der Anzahl und den Typen ihrer Parameter. Die Signatur einer Methode enthält insbesondere nicht den Rückgabetyp oder Parametermodifizierer wie Optional, ByRef oder ParamArray. Das folgende Beispiel zeigt eine Klasse mit einer Reihe von Überladungen:

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

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

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

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

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

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

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


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

Die Ausgabe des Programms ist:

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

Überladungen, die sich nur in optionalen Parametern unterscheiden, können für die "Versionsverwaltung" von Bibliotheken verwendet werden. Beispielsweise kann v1 einer Bibliothek eine Funktion mit optionalen Parametern enthalten:

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

Dann möchte v2 der Bibliothek einen weiteren optionalen Parameter "password" hinzufügen und dies tun, ohne die Quellkompatibilität zu unterbrechen (damit Anwendungen, die für v1 verwendet wurden, neu kompiliert werden können), und ohne binäre Kompatibilität zu unterbrechen (damit Anwendungen, die zum Verweisen auf v1 verwendet wurden, jetzt ohne erneute Kompilierung auf v2 verweisen können). So sieht v2 aus:

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

Beachten Sie, dass optionale Parameter in einer öffentlichen API nicht CLS-kompatibel sind. Sie können jedoch mindestens von Visual Basic und C#4 und F# genutzt werden.

Deklarationen der regulären, Async- und Iterator-Methode

Es gibt zwei Methodentypen: Unterroutinen, die keine Werte zurückgeben, und Funktionen, die dies tun. Der Textkörper und End das Konstrukt einer Methode dürfen nur weggelassen werden, wenn die Methode in einer Schnittstelle definiert ist oder über den MustOverride Modifizierer verfügt. Wenn für eine Funktion kein Rückgabetyp angegeben wird und strenge Semantik verwendet wird, tritt ein Kompilierungszeitfehler auf; andernfalls ist der Typ implizit Object oder der Typ des Typzeichens der Methode. Die Barrierefreiheitsdomäne des Rückgabetyps und der Parametertypen einer Methode muss mit der oder einer Obermenge der Barrierefreiheitsdomäne der Methode selbst übereinstimmen.

Eine normale Methode ist eine Methode mit weder Async modifizierern noch Iterator Modifizierern. Es kann sich um eine Unterroutine oder eine Funktion sein. Abschnitt "Reguläre Methoden" enthält Details dazu, was geschieht, wenn eine reguläre Methode aufgerufen wird.

Eine Iteratormethode ist eine mit dem Iterator Modifizierer und ohne Async Modifizierer. Es muss eine Funktion sein IEnumerator, und der Rückgabetyp muss , IEnumerableoder IEnumerator(Of T)IEnumerable(Of T) für einige T, und es darf keine ByRef Parameter enthalten. Section Iterator Methods details what happens when an iterator method is invoked.

Eine asynchrone Methode ist eine methode mit dem Async Modifizierer und ohne Iterator Modifizierer. Es muss entweder eine Unterroutine oder eine Funktion mit Rückgabetyp Task oder Task(Of T) für einige Tsein und darf keine ByRef Parameter enthalten. Section Async Methods details what happens when async method is invoked.

Es handelt sich um einen Kompilierungszeitfehler, wenn eine Methode nicht einer dieser drei Methodentypen ist.

Subroutine- und Funktionsdeklarationen sind speziell dafür, dass ihre Anfangs- und Endanweisungen jeweils am Anfang einer logischen Zeile beginnen müssen. Darüber hinaus muss der Textkörper einer Nicht-SubroutineMustOverride oder Funktionsdeklaration am Anfang einer logischen Zeile beginnen. Beispiel:

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

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

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

Externe Methodendeklarationen

Eine externe Methodendeklaration führt eine neue Methode ein, deren Implementierung außerhalb des Programms bereitgestellt wird.

ExternalMethodDeclaration
    : ExternalSubDeclaration
    | ExternalFunctionDeclaration
    ;

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

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

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

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

LibraryClause
    : 'Lib' StringLiteral
    ;

AliasClause
    : 'Alias' StringLiteral
    ;

Da eine externe Methodendeklaration keine tatsächliche Implementierung bereitstellt, verfügt sie über keinen Methodentext oder End kein Konstrukt. Externe Methoden werden implizit freigegeben, verfügen möglicherweise nicht über Typparameter und behandeln keine Ereignisse oder implementieren Schnittstellenmember. Wenn für eine Funktion kein Rückgabetyp angegeben wird und strenge Semantik verwendet wird, tritt ein Kompilierungszeitfehler auf. Andernfalls ist der Typ implizit Object oder der Typ des Typzeichens der Methode. Die Barrierefreiheitsdomäne des Rückgabetyps und der Parametertypen einer externen Methode muss mit oder eine Obermenge der Barrierefreiheitsdomäne der externen Methode selbst übereinstimmen.

Die Bibliotheksklausel einer externen Methodendeklaration gibt den Namen der externen Datei an, die die Methode implementiert. Die optionale Aliasklausel ist eine Zeichenfolge, die die numerische Ordnungszahl (präfixiert durch ein # Zeichen) oder den Namen der Methode in der externen Datei angibt. Ein Modifizierer mit einem einzelnen Zeichensatz kann auch angegeben werden, wodurch der Zeichensatz bestimmt wird, der während eines Aufrufs der externen Methode zum Marshallen von Zeichenfolgen verwendet wird. Der Unicode Modifizierer marshallt alle Zeichenfolgen an Unicode-Werte, der Ansi Modifizierer marshallt alle Zeichenfolgen an ANSI-Werte, und der Auto Modifizierer marshallt die Zeichenfolgen gemäß .NET Framework-Regeln basierend auf dem Namen der Methode oder den Aliasnamen, falls angegeben. Wenn kein Modifizierer angegeben ist, ist Ansider Standardwert .

Wenn Ansi oder Unicode angegeben wird, wird der Methodenname in der externen Datei ohne Änderung nachschlagen. Wenn Auto angegeben, hängt der Methodennamesuchvorgang von der Plattform ab. Wenn die Plattform als ANSI (z. B. Windows 95, Windows 98, Windows ME) betrachtet wird, wird der Methodenname ohne Änderung nachschlagen. Wenn die Suche fehlschlägt, wird eine A angefügt, und die Suche wurde erneut versucht. Wenn die Plattform als Unicode gilt (z. B. Windows NT, Windows 2000, Windows XP), wird eine W angefügt, und der Name wird nachschlagen. Wenn die Suche fehlschlägt, wird die Suche erneut ohne die W. Beispiel:

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

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

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

Datentypen, die an externe Methoden übergeben werden, werden gemäß den .NET Framework-Datenmarsingkonventionen mit einer Ausnahme gemarstet. Zeichenfolgenvariablen, die durch den Wert (d. h., ) übergeben werden, ByVal x As Stringwerden an den BSTR-Typ der OLE-Automatisierung gemarstet, und Änderungen, die an der BSTR in der externen Methode vorgenommen wurden, werden im Zeichenfolgenargument zurückgespiegelt. Dies liegt daran, dass der Typ String in externen Methoden änderbar ist und diese spezielle Marshalling das Verhalten imitiert. Zeichenfolgenparameter, die per Verweis (d. h. ByRef x As String) übergeben werden, werden als Zeiger auf den BSTR-Typ der OLE-Automatisierung gemarstet. Es ist möglich, diese speziellen Verhaltensweisen durch Angeben des System.Runtime.InteropServices.MarshalAsAttribute Attributs für den Parameter außer Kraft zu setzen.

Das Beispiel veranschaulicht die Verwendung externer Methoden:

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

Außerkraftsetzungsmethoden

Der Overridable Modifizierer gibt an, dass eine Methode außer Kraft gesetzt werden kann. Der Overrides Modifizierer gibt an, dass eine Methode eine überschreibbare Basistypmethode außer Kraft setzt, die dieselbe Signatur aufweist. Der NotOverridable Modifizierer gibt an, dass eine überschreibbare Methode nicht weiter außer Kraft gesetzt werden kann. Der MustOverride Modifizierer gibt an, dass eine Methode in abgeleiteten Klassen überschrieben werden muss.

Bestimmte Kombinationen dieser Modifizierer sind ungültig:

  • Overridable und NotOverridable schließen sich gegenseitig aus und können nicht kombiniert werden.

  • MustOverride impliziert Overridable (und kann dies nicht angeben) und kann nicht mit NotOverridable.

  • NotOverridable kann nicht mit Overridable oder MustOverride kombiniert werden und muss mit Overrides.

  • Overrides impliziert Overridable (und kann dies nicht angeben) und kann nicht mit MustOverride.

Es gibt auch zusätzliche Einschränkungen für überschreibbare Methoden:

  • Eine MustOverride Methode enthält möglicherweise keinen Methodentext oder ein End Konstrukt, überschreibt möglicherweise keine andere Methode und wird nur in MustInherit Klassen angezeigt.

  • Wenn eine Methode angibt Overrides und keine übereinstimmende Basismethode zum Überschreiben vorhanden ist, tritt ein Kompilierungszeitfehler auf. Eine Außerkraftsetzungsmethode darf nicht angegeben Shadowswerden.

  • Eine Methode überschreibt möglicherweise keine andere Methode, wenn die Barrierefreiheitsdomäne der Überschreibungsmethode nicht der Barrierefreiheitsdomäne der überschriebenen Methode entspricht. Die einzige Ausnahme besteht darin, dass eine Methode, die eine Methode in einer Protected Friend anderen Assembly überschreibt, die keinen Zugriff hat Friend , angeben Protected muss (nicht Protected Friend).

  • PrivateMethoden dürfen weder andere NotOverridableMethoden außer OverridableKraft MustOverridesetzen noch andere Methoden überschreiben.

  • Methoden in NotInheritable Klassen werden möglicherweise nicht deklariert Overridable oder MustOverride.

Das folgende Beispiel veranschaulicht die Unterschiede zwischen überschreibbaren und nicht überschreibbaren Methoden:

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

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

Class Derived
    Inherits Base

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

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

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

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

Im Beispiel führt die Klasse Base eine Methode F und eine Overridable Methode Gein. Die Klasse Derived führt eine neue Methode Fein, wodurch die geerbte Methode abgeschattet Fwird, und auch die geerbte Methode Güberschrieben wird. Das Beispiel erzeugt die folgende Ausgabe:

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

Beachten Sie, dass die Anweisung b.G()Derived.G und nicht Base.G aufruft. Dies liegt daran, dass der Laufzeittyp der Instanz (also Derived) anstelle des Kompilierungszeittyps der Instanz (was heißt Base) die tatsächliche Methodenimplementierung bestimmt, die aufgerufen werden soll.

Freigegebene Methoden

Der Shared Modifizierer gibt an, dass eine Methode eine freigegebene Methode ist. Eine freigegebene Methode funktioniert nicht für eine bestimmte Instanz eines Typs und kann direkt von einem Typ aufgerufen werden, anstatt über eine bestimmte Instanz eines Typs. Es ist jedoch gültig, eine Instanz zu verwenden, um eine freigegebene Methode zu qualifizieren. Es ist ungültig, auf , oder MyClassMyBase in einer freigegebenen Methode zu verweisenMe. Freigegebene Methoden sind Overridablemöglicherweise nicht , NotOverridableoder MustOverride, und sie überschreiben keine Methoden. Methoden, die in Standardmodulen und Schnittstellen definiert sind, dürfen nicht angegeben Sharedwerden, da sie implizit Shared bereits vorhanden sind.

Eine in einer Struktur oder Klasse ohne Shared Modifizierer deklarierte Methode ist eine Instanzmethode. Eine Instanzmethode wird auf einer bestimmten Instanz eines Typs ausgeführt. Instanzmethoden können nur über eine Instanz eines Typs aufgerufen werden und können über den Me Ausdruck auf die Instanz verweisen.

Das folgende Beispiel veranschaulicht die Regeln für den Zugriff auf freigegebene und Instanzmitglieder:

Class Test
    Private x As Integer
    Private Shared y As Integer

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

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

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

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

Die Methode F zeigt, dass in einem Instanzfunktionsmember ein Bezeichner für den Zugriff auf Instanzmember und freigegebene Member verwendet werden kann. Die Methode G zeigt, dass in einem freigegebenen Funktionselement ein Fehler beim Zugriff auf ein Instanzmitglied über einen Bezeichner vorliegt. Die Methode Main zeigt, dass in einem Memberzugriffsausdruck Instanzmember über Instanzen zugegriffen werden muss, aber auf freigegebene Member kann über Typen oder Instanzen zugegriffen werden.

Methodenparameter

Ein Parameter ist eine Variable, die verwendet werden kann, um Informationen an und aus einer Methode zu übergeben. Parameter einer Methode werden durch die Parameterliste der Methode deklariert, die aus einem oder mehreren Durch Kommas getrennten Parametern besteht.

ParameterList
    : Parameter ( Comma Parameter )*
    ;

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

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

ParameterIdentifier
    : Identifier IdentifierModifiers
    ;

Wenn kein Typ für einen Parameter angegeben und strenge Semantik verwendet wird, tritt ein Kompilierungszeitfehler auf. Andernfalls ist Object der Standardtyp oder der Typ des Typzeichens des Parameters. Auch unter der zulässigen Semantik müssen alle Parameter Typen angeben, wenn ein Parameter eine As Klausel enthält.

Parameter werden als Wert-, Bezugs-, Optional- oder Paramarrayparameter durch die Modifizierer ByVal, ByRef, , Optionalbzw ParamArray. . . Ein Parameter, der nicht angibt ByRef oder ByVal standardmäßig auf ByVal.

Parameternamen werden auf den gesamten Textkörper der Methode angewendet und sind immer öffentlich zugänglich. Ein Methodenaufruf erstellt eine Kopie, die für diesen Aufruf spezifisch ist, der Parameter und die Argumentliste des Aufrufs weist den neu erstellten Parametern Werte oder Variablenverweise zu. Da externe Methodendeklarationen und Stellvertretungsdeklarationen keinen Text haben, sind doppelte Parameternamen in Parameterlisten zulässig, aber davon abgeraten.

Auf den Bezeichner kann der Modifizierer ? für nullwerte Namen folgen, um anzugeben, dass er nullfähig ist, und auch durch Arraynamenmodifizierer, um anzugeben, dass es sich um ein Array handelt. Sie können kombiniert werden, z. B. "ByVal x?() As Integer". Es ist nicht zulässig, explizite Arraygrenzen zu verwenden; Wenn der Modifizierer für nullwerte Namen vorhanden ist, muss eine As Klausel vorhanden sein.

Wertparameter

Ein Wertparameter wird mit einem expliziten ByVal Modifizierer deklariert. Wenn der ByVal Modifizierer verwendet wird, kann der ByRef Modifizierer nicht angegeben werden. Ein Wertparameter tritt mit dem Aufruf des Elements auf, zu dem der Parameter gehört, und wird mit dem Wert des arguments initialisiert, das im Aufruf angegeben wird. Ein Wertparameter ist nicht mehr vorhanden, wenn das Element zurückgegeben wird.

Eine Methode darf einem Wertparameter neue Werte zuweisen. Solche Zuordnungen wirken sich nur auf den lokalen Speicherort aus, der durch den Wertparameter dargestellt wird; sie haben keine Auswirkungen auf das tatsächliche Argument, das im Methodenaufruf angegeben wird.

Ein Wertparameter wird verwendet, wenn der Wert eines Arguments an eine Methode übergeben wird, und Änderungen des Parameters wirken sich nicht auf das ursprüngliche Argument aus. Ein Wertparameter bezieht sich auf eine eigene Variable, die sich von der Variablen des entsprechenden Arguments unterscheidet. Diese Variable wird durch Kopieren des Werts des entsprechenden Arguments initialisiert. Das folgende Beispiel zeigt eine Methode F mit dem Namen p"Value":

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

    Sub Main()
        Dim a As Integer = 1

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

Das Beispiel erzeugt die folgende Ausgabe, auch wenn der Wertparameter p geändert wird:

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

Referenzparameter

Ein Verweisparameter ist ein Parameter, der mit einem ByRef Modifizierer deklariert wird. Wenn der Modifizierer angegeben ist, kann der ByRefByVal Modifizierer nicht verwendet werden. Ein Verweisparameter erstellt keinen neuen Speicherort. Stattdessen stellt ein Verweisparameter die Variable dar, die als Argument in der Methode oder im Konstruktoraufruf angegeben wird. Konzeptionell ist der Wert eines Referenzparameters immer identisch mit der zugrunde liegenden Variablen.

Referenzparameter fungieren in zwei Modi, entweder als Aliase oder über kopieren-in-Kopie zurück.

Decknamen. Ein Verweisparameter wird verwendet, wenn der Parameter als Alias für ein vom Aufrufer bereitgestelltes Argument fungiert. Ein Verweisparameter definiert keine Variable, sondern verweist stattdessen auf die Variable des entsprechenden Arguments. Änderungen eines Verweisparameters direkt und unmittelbar wirken sich auf das entsprechende Argument aus. Das folgende Beispiel zeigt eine Methode Swap mit zwei Referenzparametern:

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

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

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

Die Ausgabe des Programms ist:

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

Stellt für den Aufruf der Methode Swap in der Klasse Maina dar x, und b stellt sie dary. Daher hat der Aufruf die Auswirkung, die Werte von x und y zu vertauschen.

In einer Methode, die Referenzparameter verwendet, ist es möglich, dass mehrere Namen denselben Speicherort darstellen:

Module Test
    Private s As String

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

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

Im Beispiel übergibt der Aufruf der Methode FG einen Verweis auf s beide a und b. Daher beziehen sich für diesen Aufruf die Namen s, aund b alle auf denselben Speicherort, und die drei Zuordnungen ändern alle die Instanzvariable s.

Copy-in copy-back. Wenn der Typ der Variablen, die an einen Verweisparameter übergeben wird, nicht mit dem Typ des Referenzparameters kompatibel ist oder eine nicht variable (z. B. eine Eigenschaft) als Argument an einen Verweisparameter übergeben wird oder der Aufruf spät gebunden ist, wird eine temporäre Variable zugewiesen und an den Referenzparameter übergeben. Der übergebene Wert wird vor dem Aufrufen der Methode in diese temporäre Variable kopiert und zurück in die ursprüngliche Variable kopiert (sofern vorhanden und beschreibbar), wenn die Methode zurückgegeben wird. Daher kann ein Verweisparameter nicht unbedingt einen Verweis auf den genauen Speicher der Übergebenen Variable enthalten, und alle Änderungen am Referenzparameter werden möglicherweise erst dann in der Variablen widergespiegelt, wenn die Methode beendet wird. Beispiel:

Class Base
End Class

Class Derived
    Inherits Base
End Class

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

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

    Sub Main()
        Dim d As Derived

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

Im Falle des ersten Aufrufs von F, wird eine temporäre Variable erstellt, und der Wert der Eigenschaft G wird ihm zugewiesen und übergeben.F Wenn der Wert in der temporären Variablen zurückgegeben Fwird, wird der Eigenschaft von G. Im zweiten Fall wird eine weitere temporäre Variable erstellt und der Wert d zugewiesen und an Fdiese übergeben. Wenn der Wert in der temporären Variablen zurückgegeben Fwird, Derivedwird er in den Typ der Variablen zurückgesetzt und zugewiesen d. Da der übergebene Wert nicht in eine Umwandlung umgewandelt Derivedwerden kann, wird zur Laufzeit eine Ausnahme ausgelöst.

Optionale Parameter

Ein optionaler Parameter wird mit dem Optional Modifizierer deklariert. Parameter, die einem optionalen Parameter in der formalen Parameterliste folgen, müssen ebenfalls optional sein; wenn sie den Optional Modifizierer für die folgenden Parameter nicht angeben, wird ein Kompilierungszeitfehler ausgelöst. Ein optionaler Parameter eines Typs mit nullablem Typ oder nicht nullablem Typ T?T muss einen Konstantenausdruck e angeben, der als Standardwert verwendet werden soll, wenn kein Argument angegeben wird. Wenn e der Wert vom Typ "Object" ausgewertet Nothing wird, wird der Standardwert des Parametertyps als Standard für den Parameter verwendet. CType(e, T) Andernfalls muss es sich um einen konstanten Ausdruck handeln, der als Standard für den Parameter verwendet wird.

Optionale Parameter sind die einzige Situation, in der ein Initialisierer für einen Parameter gültig ist. Die Initialisierung erfolgt immer als Teil des Aufrufausdrucks, nicht innerhalb des Methodentexts selbst.

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

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

Die Ausgabe des Programms ist:

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

Optionale Parameter können weder in Delegaten- oder Ereignisdeklarationen noch in Lambda-Ausdrücken angegeben werden.

ParamArray-Parameter

ParamArray Parameter werden mit dem ParamArray Modifizierer deklariert. Wenn der Modifizierer vorhanden ist, muss der ParamArrayByVal Modifizierer angegeben werden, und kein anderer Parameter kann den ParamArray Modifizierer verwenden. Der ParamArray Typ des Parameters muss ein eindimensionales Array sein, und er muss der letzte Parameter in der Parameterliste sein.

Ein ParamArray Parameter stellt eine unbestimmte Anzahl von Parametern des Typs des .ParamArray Innerhalb der Methode selbst wird ein ParamArray Parameter als deklarierter Typ behandelt und hat keine spezielle Semantik. Ein ParamArray Parameter ist implizit optional, mit einem Standardwert eines leeren eindimensionalen Arrays des Typs des .ParamArray

Ein ParamArray Argument kann auf eine von zwei Arten in einem Methodenaufruf angegeben werden:

  • Das für ein ParamArray Argument angegebene Argument kann ein einzelner Ausdruck eines Typs sein, der auf den ParamArray Typ erweitert wird. In diesem Fall verhält sich dies ParamArray genau wie ein Wertparameter.

  • Alternativ kann der Aufruf null oder mehr Argumente für das ParamArrayArgument angeben, wobei jedes Argument ein Ausdruck eines Typs ist, der implizit in den Elementtyp des Elements ParamArrayumgewandelt wird. In diesem Fall erstellt der Aufruf eine Instanz des ParamArray Typs mit einer Länge, die der Anzahl der Argumente entspricht, initialisiert die Elemente der Arrayinstanz mit den angegebenen Argumentwerten und verwendet die neu erstellte Arrayinstanz als tatsächliches Argument.

Abgesehen davon, dass eine variable Anzahl von Argumenten in einem Aufruf zugelassen wird, entspricht dies ParamArray genau einem Wertparameter desselben Typs, wie das folgende Beispiel veranschaulicht.

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

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

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

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

Das Beispiel erzeugt die Ausgabe.

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

Der erste Aufruf des F Arrays übergibt das Array a einfach als Wertparameter. Der zweite Aufruf von automatisch erstellt ein Array mit F vier Elementen mit den angegebenen Elementwerten und übergibt diese Arrayinstanz als Wertparameter. Ebenso erstellt der dritte Aufruf eines F Nullelementarrays ein Nullelementarray und übergibt diese Instanz als Wertparameter. Die zweiten und dritten Aufrufe entsprechen genau dem Schreiben:

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

ParamArray Parameter können in Stellvertretungs- oder Ereignisdeklarationen nicht angegeben werden.

Ereignisbehandlung

Methoden können Ereignisse, die von Objekten in instanzen oder freigegebenen Variablen ausgelöst werden, deklarativ behandeln. Zum Behandeln von Ereignissen gibt eine Methodendeklaration das Handles Schlüsselwort an und listet ein oder mehrere Ereignisse auf.

HandlesClause
    : ( 'Handles' EventHandlesList )?
    ;

EventHandlesList
    : EventMemberSpecifier ( Comma EventMemberSpecifier )*
    ;

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

Ein Ereignis in der Handles Liste wird durch zwei Bezeichner durch einen Punkt getrennt angegeben:

  • Der erste Bezeichner muss eine Instanz oder freigegebene Variable im enthaltenden Typ sein, die den Modifizierer oder das WithEvents oder MyClassMe das MyBase Schlüsselwort angibt. Andernfalls tritt ein Kompilierungszeitfehler auf. Diese Variable enthält das Objekt, das die von dieser Methode behandelten Ereignisse auslöst.

  • Der zweite Bezeichner muss ein Element des Typs des ersten Bezeichners angeben. Das Mitglied muss ein Ereignis sein und kann freigegeben werden. Wenn für den ersten Bezeichner eine freigegebene Variable angegeben wird, muss das Ereignis freigegeben werden, oder es wird ein Fehlerergebnis ausgegeben.

Eine Handlermethode M gilt als gültiger Ereignishandler für ein Ereignis E , wenn die Anweisung AddHandler E, AddressOf M ebenfalls gültig wäre. Im Gegensatz zu einer AddHandler Anweisung ermöglichen explizite Ereignishandler jedoch das Behandeln eines Ereignisses mit einer Methode ohne Argumente, unabhängig davon, ob strenge Semantik verwendet wird oder nicht:

Option Strict On

Class C1
    Event E(x As Integer)
End Class

Class C2
    withEvents C1 As New C1()

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

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

Ein einzelnes Mitglied kann mehrere übereinstimmende Ereignisse behandeln, und mehrere Methoden können ein einzelnes Ereignis behandeln. Die Barrierefreiheit einer Methode hat keine Auswirkungen auf die Fähigkeit, Ereignisse zu behandeln. Das folgende Beispiel zeigt, wie eine Methode Ereignisse behandeln kann:

Class Raiser
    Event E1()

    Sub Raise()
        RaiseEvent E1
    End Sub
End Class

Module Test
    WithEvents x As Raiser

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

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

Dies wird ausgegeben:

Raised
Raised

Ein Typ erbt alle Ereignishandler, die vom Basistyp bereitgestellt werden. Ein abgeleiteter Typ kann die ereigniszuordnungen, die er von seinen Basistypen erbt, nicht ändern, kann dem Ereignis jedoch weitere Handler hinzufügen.

Erweiterungsmethoden

Methoden können Typen von außerhalb der Typdeklaration mithilfe von Erweiterungsmethoden hinzugefügt werden. Erweiterungsmethoden sind Methoden, auf die das System.Runtime.CompilerServices.ExtensionAttribute Attribut angewendet wird. Sie können nur in Standardmodulen deklariert werden und müssen mindestens einen Parameter aufweisen, der den Typ angibt, den die Methode erweitert. Die folgende Erweiterungsmethode erweitert z. B. den Typ String:

Imports System.Runtime.CompilerServices

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

Hinweis: Obwohl Visual Basic Erweiterungsmethoden in einem Standardmodul deklariert werden muss, können andere Sprachen wie C# sie möglicherweise in anderen Typen deklariert werden. Solange die Methoden den hier beschriebenen anderen Konventionen folgen und der enthaltende Typ kein offener generischer Typ ist und nicht instanziiert werden kann, erkennt Visual Basic die Erweiterungsmethoden.

Wenn eine Erweiterungsmethode aufgerufen wird, wird die Instanz, für die sie aufgerufen wird, an den ersten Parameter übergeben. Der erste Parameter kann nicht deklariert oder ParamArraydeklariert Optional werden. Jeder Typ, einschließlich eines Typparameters, kann als erster Parameter einer Erweiterungsmethode angezeigt werden. Die folgenden Methoden erweitern z. B. die Typen Integer(), alle implementierten System.Collections.Generic.IEnumerable(Of T)Typen und alle Typen:

Imports System.Runtime.CompilerServices

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

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

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

Wie im vorherigen Beispiel gezeigt, können Schnittstellen erweitert werden. Schnittstellenerweiterungsmethoden stellen die Implementierung der Methode bereit, sodass Typen, die eine Schnittstelle implementieren, für die Erweiterungsmethoden definiert sind, weiterhin nur die elemente implementieren, die ursprünglich von der Schnittstelle deklariert wurden. Beispiel:

Imports System.Runtime.CompilerServices

Interface IAction
  Sub DoAction()
End Interface

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

Class C
  Implements IAction

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

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

Erweiterungsmethoden können auch Typeinschränkungen für ihre Typparameter aufweisen, und ebenso wie bei nicht-erweiterungsgenerischen Methoden können Typargumente abgeleitet werden:

Imports System.Runtime.CompilerServices

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

Auf Erweiterungsmethoden kann auch über implizite Instanzausdrücke innerhalb des erweiterten Typs zugegriffen werden:

Imports System.Runtime.CompilerServices

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

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

Für die Zwecke der Barrierefreiheit werden Erweiterungsmethoden auch als Member des Standardmoduls behandelt, in dem sie deklariert sind - sie haben keinen zusätzlichen Zugriff auf die Member des Typs, die sie über den Zugriff hinaus erweitern, den sie aufgrund ihres Deklarationskontexts haben.

Erweiterungsmethoden sind nur verfügbar, wenn sich die Standardmodulmethode im Bereich befindet. Andernfalls wird der ursprüngliche Typ nicht erweitert. Beispiel:

Imports System.Runtime.CompilerServices

Class C1
End Class

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

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

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

Wenn sie auf einen Typ verweisen, wenn nur eine Erweiterungsmethode für den Typ verfügbar ist, wird weiterhin ein Kompilierungszeitfehler erzeugt.

Es ist wichtig zu beachten, dass Erweiterungsmethoden als Member des Typs in allen Kontexten betrachtet werden, in denen Member gebunden sind, z. B. das stark typierte For Each Muster. Beispiel:

Imports System.Runtime.CompilerServices

Class C1
End Class

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

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

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

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

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

Stellvertretungen können auch erstellt werden, die auf Erweiterungsmethoden verweisen. Der Code:

Delegate Sub D1()

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

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

ist ungefähr gleichbedeutend mit:

Delegate Sub D1()

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

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

Hinweis: Visual Basic fügt normalerweise eine Überprüfung eines Instanzmethodenaufrufs ein, der dazu führt, dass eine System.NullReferenceException Instanz auftritt, für Nothingdie die Methode aufgerufen wird. Im Falle von Erweiterungsmethoden gibt es keine effiziente Möglichkeit, diese Überprüfung einzufügen, sodass Erweiterungsmethoden explizit überprüft Nothingwerden müssen.

Hinweis: Ein Werttyp wird boxt, wenn er ByVal als Argument an einen Parameter übergeben wird, der als Schnittstelle eingegeben wird. Dies bedeutet, dass Nebenwirkungen der Erweiterungsmethode anstelle des Originals auf eine Kopie der Struktur angewendet werden. Die Sprache legt zwar keine Einschränkungen für das erste Argument einer Erweiterungsmethode fest, es wird jedoch empfohlen, dass Erweiterungsmethoden nicht zum Erweitern von Werttypen verwendet werden oder dass beim Erweitern von Werttypen der erste Parameter übergeben ByRef wird, um sicherzustellen, dass Nebenwirkungen mit dem ursprünglichen Wert funktionieren.

Partielle Methoden

Eine partielle Methode ist eine Methode, die eine Signatur angibt, aber nicht den Textkörper der Methode. Der Textkörper der Methode kann von einer anderen Methodendeklaration mit demselben Namen und derselben Signatur bereitgestellt werden, höchstwahrscheinlich in einer anderen Teildeklaration des Typs. Beispiel:

a.vb:

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

    Public Sub New()
        ' Initialize controls
        ...

        ValidateControls()
    End Sub    
End Class

b.vb:

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

In diesem Beispiel deklariert eine partielle Deklaration der Klasse MyForm eine partielle Methode ValidateControls ohne Implementierung. Der Konstruktor in der Teildeklaration ruft die Partielle Methode auf, obwohl in der Datei kein Textkörper angegeben ist. Die andere Teildeklaration liefert MyForm dann die Implementierung der Methode.

Partielle Methoden können aufgerufen werden, unabhängig davon, ob ein Körper bereitgestellt wurde; wenn kein Methodentext angegeben wird, wird der Aufruf ignoriert. Beispiel:

Public Class C1
    Private Partial Sub M1()
    End Sub

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

Alle Ausdrücke, die als Argumente an einen ignorierten partiellen Methodenaufruf übergeben werden, werden ebenfalls ignoriert und nicht ausgewertet. (Hinweis. Dies bedeutet, dass Partielle Methoden eine sehr effiziente Möglichkeit sind, Verhalten bereitzustellen, das über zwei Teiltypen definiert wird, da die Partielle Methoden keine Kosten haben, wenn sie nicht verwendet werden.)

Die partielle Methodendeklaration muss als Private subroutine ohne Anweisungen im Text deklariert werden und muss immer eine Unterroutine sein. Partielle Methoden können keine Schnittstellenmethoden selbst implementieren, obwohl die Methode, die ihren Text bereitstellt, möglich ist.

Nur eine Methode kann einen Textkörper zu einer partiellen Methode bereitstellen. Eine Methode, die einen Textkörper an eine Partielle Methode angibt, muss dieselbe Signatur wie die partielle Methode, die gleichen Einschränkungen für alle Typparameter, dieselben Deklarationsmodifizierer und die gleichen Parameter- und Typparameternamen aufweisen. Attribute für die partielle Methode und die Methode, die ihren Text bereitstellt, werden zusammengeführt, ebenso wie attribute für die Parameter der Methoden. Ebenso wird die Liste der Ereignisse, die von den Methoden behandelt werden, zusammengeführt. Beispiel:

Class C1
    Event E1()
    Event E2()

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

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

Erbauer

Konstruktoren sind spezielle Methoden, die die Kontrolle über die Initialisierung ermöglichen. Sie werden ausgeführt, nachdem das Programm beginnt oder wenn eine Instanz eines Typs erstellt wird. Im Gegensatz zu anderen Membern werden Konstruktoren nicht geerbt und führen keinen Namen in den Deklarationsbereich eines Typs ein. Konstruktoren können nur durch Objekterstellungsausdrücke oder durch .NET Framework aufgerufen werden; sie werden möglicherweise nie direkt aufgerufen.

Hinweis: Konstruktoren haben die gleiche Einschränkung für die Zeilenplatzierung, die Unterroutinen aufweisen. Die Anfangsanweisung, end-Anweisung und der Block müssen alle am Anfang einer logischen Zeile angezeigt werden.

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

ConstructorModifier
    : AccessModifier
    | 'Shared'
    ;

Instanzkonstruktoren

Instanzkonstruktoren initialisieren Instanzen eines Typs und werden vom .NET Framework ausgeführt, wenn eine Instanz erstellt wird. Die Parameterliste eines Konstruktors unterliegt den gleichen Regeln wie die Parameterliste einer Methode. Instanzkonstruktoren können überladen werden.

Alle Konstruktoren in Referenztypen müssen einen anderen Konstruktor aufrufen. Wenn der Aufruf explizit ist, muss es sich um die erste Anweisung im Konstruktormethodentext handeln. Die Anweisung kann entweder einen anderen Instanzkonstruktor des Typs aufrufen , z Me.New(...) . B. oder - oder MyClass.New(...) wenn es sich nicht um eine Struktur handelt, kann sie einen Instanzkonstruktor des Basistyps des Typs aufrufen , z MyBase.New(...). B. . . Es ist ungültig, dass ein Konstruktor sich selbst aufruft. Wenn ein Konstruktor einen Aufruf eines anderen Konstruktors ausgelassen, MyBase.New() ist implizit. Wenn kein Parameterloser Basistypkonstruktor vorhanden ist, tritt ein Kompilierungszeitfehler auf. Da Me die Parameter erst nach dem Aufruf eines Basisklassenkonstruktors erstellt werden, können die Parameter für eine Konstruktoraufrufanweisung nicht auf , oder MyClassimplizit oder MyBase explizit verweisenMe.

Wenn die erste Anweisung eines Konstruktors der Form MyBase.New(...)entspricht, führt der Konstruktor implizit die Initialisierungen aus, die von den Variableninitialisierern der im Typ deklarierten Instanzvariablen angegeben werden. Dies entspricht einer Abfolge von Zuordnungen, die unmittelbar nach dem Aufrufen des direkten Basistypkonstruktors ausgeführt werden. Diese Sortierung stellt sicher, dass alle Basisinstanzvariablen durch ihre Variableninitialisierer initialisiert werden, bevor Anweisungen ausgeführt werden, die Zugriff auf die Instanz haben. Beispiel:

Class A
    Protected x As Integer = 1
End Class

Class B
    Inherits A

    Private y As Integer = x

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

Wenn New B() sie zum Erstellen einer Instanz verwendet Bwird, wird die folgende Ausgabe erstellt:

x = 1, y = 1

Der Wert y liegt 1 daran, dass der Variableninitialisierer ausgeführt wird, nachdem der Basisklassenkonstruktor aufgerufen wurde. Variable Initialisierer werden in der Textreihenfolge ausgeführt, in der sie in der Typdeklaration angezeigt werden.

Wenn ein Typ nur Private Konstruktoren deklariert, ist es im Allgemeinen nicht möglich, dass andere Typen vom Typ abgeleitet werden oder Instanzen des Typs erstellen. Die einzige Ausnahme ist Typen, die innerhalb des Typs geschachtelt sind. Private Konstruktoren werden häufig in Typen verwendet, die nur Shared Member enthalten.

Wenn ein Typ keine Instanzkonstruktordeklarationen enthält, wird automatisch ein Standardkonstruktor bereitgestellt. Der Standardkonstruktor ruft einfach den parameterlosen Konstruktor des direkten Basistyps auf. Wenn der direkte Basistyp keinen Konstruktor ohne Zugriff hat, tritt ein Kompilierungszeitfehler auf. Der deklarierte Zugriffstyp für den Standardkonstruktor ist Public , es sei denn, der Typ ist MustInherit, in diesem Fall ist Protectedder Standardkonstruktor .

Hinweis: Der Standardzugriff für den Standardkonstruktor eines MustInherit Typs besteht Protected darin, dass MustInherit Klassen nicht direkt erstellt werden können. Es gibt also keinen Sinn, den Standardkonstruktor Publiczu machen.

Im folgenden Beispiel wird ein Standardkonstruktor bereitgestellt, da die Klasse keine Konstruktordeklarationen enthält:

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

Daher entspricht das Beispiel genau folgendem:

Class Message
    Dim sender As Object
    Dim text As String

    Sub New()
    End Sub
End Class

Standardkonstruktoren, die in eine mit dem Attribut Microsoft.VisualBasic.CompilerServices.DesignerGeneratedAttribute gekennzeichnete designergenerierte Klasse ausgegeben werden, rufen nach dem Aufruf des Basiskonstruktors die Methode Sub InitializeComponent()auf, sofern vorhanden. (Hinweis. Auf diese Weise können vom Designer generierte Dateien, z. B. vom WinForms-Designer erstellte Dateien, den Konstruktor in der Designerdatei weglassen. Dies ermöglicht es dem Programmierer, es selbst anzugeben, falls dies der Fall ist.)

Freigegebene Konstruktoren

Freigegebene Konstruktoren initialisieren die freigegebenen Variablen eines Typs; sie werden ausgeführt, nachdem das Programm mit der Ausführung beginnt, aber vor allen Verweisen auf ein Element des Typs. Ein freigegebener Konstruktor gibt den Shared Modifizierer an, es sei denn, er befindet sich in einem Standardmodul, in dem der Shared Modifizierer impliziert ist.

Im Gegensatz zu Instanzkonstruktoren haben freigegebene Konstruktoren impliziten öffentlichen Zugriff, verfügen über keine Parameter und rufen möglicherweise keine anderen Konstruktoren auf. Vor der ersten Anweisung in einem freigegebenen Konstruktor führt der freigegebene Konstruktor implizit die initialisierungen durch die Variableninitialisierer der freigegebenen Variablen aus, die im Typ deklariert sind. Dies entspricht einer Folge von Zuweisungen, die unmittelbar beim Eintritt in den Konstruktor ausgeführt werden. Die Variableninitialisierer werden in der Textreihenfolge ausgeführt, in der sie in der Typdeklaration angezeigt werden.

Das folgende Beispiel zeigt eine Employee Klasse mit einem freigegebenen Konstruktor, der eine freigegebene Variable initialisiert:

Imports System.Data

Class Employee
    Private Shared ds As DataSet

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

    Public Name As String
    Public Salary As Decimal
End Class

Für jeden geschlossenen generischen Typ ist ein separater freigegebener Konstruktor vorhanden. Da der freigegebene Konstruktor genau einmal für jeden geschlossenen Typ ausgeführt wird, ist es praktisch, Laufzeitüberprüfungen für den Typparameter zu erzwingen, der nicht zur Kompilierungszeit über Einschränkungen überprüft werden kann. Der folgende Typ verwendet beispielsweise einen freigegebenen Konstruktor, um zu erzwingen, dass der Typparameter lautet Integer oder Double:

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

Genau, wenn freigegebene Konstruktoren ausgeführt werden, ist hauptsächlich von der Implementierung abhängig, obwohl mehrere Garantien bereitgestellt werden, wenn ein freigegebener Konstruktor explizit definiert ist:

  • Freigegebene Konstruktoren werden vor dem ersten Zugriff auf ein statisches Feld des Typs ausgeführt.

  • Freigegebene Konstruktoren werden vor dem ersten Aufruf einer statischen Methode des Typs ausgeführt.

  • Freigegebene Konstruktoren werden vor dem ersten Aufruf eines Konstruktors für den Typ ausgeführt.

Die oben genannten Garantien gelten nicht in der Situation, in der ein gemeinsam genutzter Konstruktor implizit für gemeinsam genutzte Initialisierer erstellt wird. Die Ausgabe aus dem folgenden Beispiel ist unsicher, da die genaue Reihenfolge des Ladens und daher der Ausführung gemeinsam genutzter Konstruktoren nicht definiert ist:

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

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

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

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

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

Die Ausgabe kann eine der folgenden Sein:

Init A
A.F
Init B
B.F

oder

Init B
Init A
A.F
B.F

Im folgenden Beispiel wird dagegen eine vorhersagbare Ausgabe erzeugt. Beachten Sie, dass der Shared Konstruktor für die Klasse A nie ausgeführt wird, obwohl die Klasse B von ihr abgeleitet wird:

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

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

Class B
    Inherits A

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

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

Die Ausgabe lautet:

Init B
B.G

Es ist auch möglich, Zirkelabhängigkeiten zu erstellen, mit Shared denen Variablen mit Variableninitialisierern im Standardwertzustand beobachtet werden können, wie im folgenden Beispiel:

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

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

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

Dadurch wird die Ausgabe erzeugt:

X = 1, Y = 2

Zum Ausführen der Main Methode lädt das System zuerst die Klasse B. Der Shared Konstruktor der Klasse B berechnet den Anfangswert von Y, der rekursiv bewirkt, dass die Klasse A geladen wird, da auf den Wert A.X verwiesen wird. Der Shared Konstruktor der Klasse A wiederum berechnet den Anfangswert von X, und ruft dabei den Standardwert von Y, der Null ist. A.X wird somit initialisiert für 1. Der Vorgang des Ladens A wird dann abgeschlossen, wobei die Berechnung des Anfangswerts zurückgegeben Ywird, der das Ergebnis wird 2.

Wenn sich die Main Methode stattdessen in der Klasse Abefindet, hätte das Beispiel die folgende Ausgabe erzeugt:

X = 2, Y = 1

Vermeiden Sie Zirkelbezüge in Shared variablen Initialisierern, da es im Allgemeinen unmöglich ist, die Reihenfolge zu bestimmen, in der Klassen mit solchen Verweisen geladen werden.

Ereignisse

Ereignisse werden verwendet, um Code über ein bestimmtes Vorkommen zu benachrichtigen. Eine Ereignisdeklaration besteht aus einem Bezeichner, einem Delegattyp oder einer Parameterliste und einer optionalen Implements Klausel.

EventMemberDeclaration
    : RegularEventMemberDeclaration
    | CustomEventMemberDeclaration
    ;

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

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

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

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

InterfaceEventModifiers
    : 'Shadows'
    ;

Wenn ein Delegattyp angegeben ist, verfügt der Delegattyp möglicherweise nicht über einen Rückgabetyp. Wenn eine Parameterliste angegeben wird, darf sie keine Parameter enthalten oder ParamArray Parameter enthaltenOptional. Die Barrierefreiheitsdomäne der Parametertypen und/oder Stellvertretungstypen muss mit der Barrierefreiheitsdomäne des Ereignisses selbst identisch sein. Ereignisse können durch Angabe des Shared Modifizierers freigegeben werden.

Neben dem Membernamen, der dem Deklarationsbereich des Typs hinzugefügt wurde, deklariert eine Ereignisdeklaration implizit mehrere andere Member. Bei einem Ereignis mit dem Namen Xwerden die folgenden Member dem Deklarationsraum hinzugefügt:

  • Wenn es sich bei der Deklaration um eine Methodendeklaration handelt, wird eine geschachtelte Delegatklasse namens XEventHandler eingeführt. Die geschachtelte Delegatklasse entspricht der Methodendeklaration und weist die gleiche Barrierefreiheit wie das Ereignis auf. Die Attribute in der Parameterliste gelten für die Parameter der Delegatenklasse.

  • Eine Private Instanzvariable, die als Stellvertretung mit dem Namen XEventeingegeben wurde.

  • Zwei Benannte add_X Methoden, die remove_X nicht aufgerufen, außer Kraft gesetzt oder überladen werden können.

Wenn ein Typ versucht, einen Namen zu deklarieren, der einem der obigen Namen entspricht, führt ein Kompilierungszeitfehler, und die impliziten add_X und remove_X Deklarationen werden für die Zwecke der Namensbindung ignoriert. Es ist nicht möglich, die eingeführten Member außer Kraft zu setzen oder zu überladen, obwohl es möglich ist, sie in abgeleiteten Typen abzuschatten. Beispielsweise die Klassendeklaration

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

entspricht der folgenden Deklaration.

Class Raiser
    Public Delegate Sub ConstructedEventHandler(i As Integer)

    Protected ConstructedEvent As ConstructedEventHandler

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

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

Das Deklarieren eines Ereignisses ohne Angabe eines Delegatentyps ist die einfachste und kompakteste Syntax, hat jedoch den Nachteil, einen neuen Delegattyp für jedes Ereignis zu deklarieren. Im folgenden Beispiel werden beispielsweise drei ausgeblendete Delegattypen erstellt, obwohl alle drei Ereignisse dieselbe Parameterliste aufweisen:

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

Im folgenden Beispiel verwenden die Ereignisse einfach denselben Delegat: EventHandler

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

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

Ereignisse können auf eine von zwei Arten behandelt werden: statisch oder dynamisch. Die statische Behandlung von Ereignissen ist einfacher und erfordert nur eine WithEvents Variable und eine Handles Klausel. Im folgenden Beispiel behandelt die Klasse Form1 das Ereignis Click des Objekts Buttonstatisch:

Public Class Form1
    Public WithEvents Button1 As New Button()

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

Die dynamische Behandlung von Ereignissen ist komplexer, da das Ereignis explizit verbunden und im Code getrennt werden muss. Die Anweisung AddHandler fügt einen Handler für ein Ereignis hinzu, und die Anweisung RemoveHandler entfernt einen Handler für ein Ereignis. Das nächste Beispiel zeigt eine KlasseForm1, die als Ereignishandler für Button1das Click Ereignis hinzufügtButton1_Click:

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

    Private Button1 As Button = New Button()

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

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

In der Methode Disconnectwird der Ereignishandler entfernt.

Benutzerdefinierte Ereignisse

Wie im vorherigen Abschnitt erläutert, definieren Ereignisdeklarationen implizit ein Feld, eine add_ Methode und eine remove_ Methode, die zum Nachverfolgen von Ereignishandlern verwendet wird. In einigen Fällen kann es jedoch wünschenswert sein, benutzerdefinierten Code für die Nachverfolgung von Ereignishandlern bereitzustellen. Wenn eine Klasse beispielsweise vierzig Ereignisse definiert, von denen nur ein paar jemals behandelt werden, kann die Verwendung einer Hashtabelle anstelle von vierzig Feldern zum Nachverfolgen der Handler für jedes Ereignis effizienter sein. Benutzerdefinierte Ereignisse ermöglichen die explizite Definition der add_Xremove_X Methoden, wodurch der benutzerdefinierte Speicher für Ereignishandler ermöglicht wird.

Benutzerdefinierte Ereignisse werden auf die gleiche Weise deklariert, wie Ereignisse, die einen Delegatentyp angeben, deklariert werden, mit der Ausnahme, dass das Schlüsselwort dem Event Schlüsselwort Custom vorausgehen muss. Eine benutzerdefinierte Ereignisdeklaration enthält drei Deklarationen: eine AddHandler Deklaration, eine RemoveHandler Deklaration und eine RaiseEvent Deklaration. Keine der Deklarationen kann über Modifizierer verfügen, obwohl sie Attribute haben können.

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

EventAccessorDeclaration
    : AddHandlerDeclaration
    | RemoveHandlerDeclaration
    | RaiseEventDeclaration
    ;

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

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

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

Beispiel:

Class Test
    Private Handlers As EventHandler

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

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

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

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

RemoveHandler Die AddHandler Deklaration verwendet einen ByVal Parameter, der vom Stellvertretungstyp des Ereignisses sein muss. Wenn eine oder RemoveHandler eine AddHandler Anweisung ausgeführt wird (oder eine Handles Klausel automatisch ein Ereignis behandelt), wird die entsprechende Deklaration aufgerufen. Die RaiseEvent Deklaration akzeptiert dieselben Parameter wie der Ereignisdelegat und wird aufgerufen, wenn eine RaiseEvent Anweisung ausgeführt wird. Alle Erklärungen müssen bereitgestellt werden und gelten als Unterroutinen.

Beachten Sie, dass AddHandlerRemoveHandler und RaiseEvent Deklarationen dieselbe Einschränkung für die Zeilenplatzierung aufweisen, die Unterroutinen aufweisen. Die Anfangsanweisung, end-Anweisung und der Block müssen alle am Anfang einer logischen Zeile angezeigt werden.

Neben dem Membernamen, der dem Deklarationsbereich des Typs hinzugefügt wurde, deklariert eine benutzerdefinierte Ereignisdeklaration implizit mehrere andere Member. Bei einem Ereignis mit dem Namen Xwerden die folgenden Member dem Deklarationsraum hinzugefügt:

  • Eine Methode namens add_X, die der AddHandler Deklaration entspricht.

  • Eine Methode namens remove_X, die der RemoveHandler Deklaration entspricht.

  • Eine Methode namens fire_X, die der RaiseEvent Deklaration entspricht.

Wenn ein Typ versucht, einen Namen zu deklarieren, der einem der obigen Namen entspricht, führt ein Kompilierungszeitfehler, und die impliziten Deklarationen werden für die Zwecke der Namensbindung ignoriert. Es ist nicht möglich, die eingeführten Member außer Kraft zu setzen oder zu überladen, obwohl es möglich ist, sie in abgeleiteten Typen abzuschatten.

Hinweis: Custom ist kein reserviertes Wort.

Benutzerdefinierte Ereignisse in WinRT-Assemblys

Ab Microsoft Visual Basic 11.0 werden Ereignisse, die in einer datei kompiliert mit /target:winmdobjoder in einer Schnittstelle in einer solchen Datei deklariert und dann an anderer Stelle implementiert werden, etwas anders behandelt.

  • Externe Tools, die zum Erstellen der Winmd verwendet werden, ermöglichen in der Regel nur bestimmte Delegattypen wie System.EventHandler(Of T) oder System.TypedEventHandle(Of T, U), und andere nicht zulassen.

  • Das XEvent Feld hat Typ System.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenTable(Of T) , wobei T es sich um den Delegattyp handelt.

  • Der AddHandler-Accessor gibt einen System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken, und der RemoveHandler-Accessor verwendet einen einzelnen Parameter desselben Typs.

Hier ist ein Beispiel für ein solches benutzerdefiniertes Ereignis.

Imports System.Runtime.InteropServices.WindowsRuntime

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

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

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

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

Konstanten

Eine Konstante ist ein Konstantenwert, der ein Element eines Typs ist.

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

ConstantModifier
    : AccessModifier
    | 'Shadows'
    ;

ConstantDeclarators
    : ConstantDeclarator ( Comma ConstantDeclarator )*
    ;

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

Konstanten werden implizit freigegeben. Wenn die Deklaration eine As Klausel enthält, gibt die Klausel den Typ des elements an, das durch die Deklaration eingeführt wurde. Wenn der Typ ausgelassen wird, wird der Typ der Konstante abgeleitet. Der Typ einer Konstante darf nur ein Grundtyp oder Objectein Grundtyp sein. Wenn eine Konstante eingegeben wird und Object kein Typzeichen vorhanden ist, ist der tatsächliche Typ der Konstante der Typ des Konstantenausdrucks. Andernfalls ist der Typ der Konstante der Typ des Typs der Konstante.

Das folgende Beispiel zeigt eine Klasse mit dem Namen Constants , die zwei öffentliche Konstanten enthält:

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

Auf Konstanten kann über die Klasse zugegriffen werden, wie im folgenden Beispiel, in dem die Werte und Constants.AConstants.Bdie Werte ausgegeben werden.

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

Eine Konstantendeklaration, die mehrere Konstanten deklariert, entspricht mehreren Deklarationen einzelner Konstanten. Im folgenden Beispiel werden drei Konstanten in einer Deklarationsanweisung deklariert.

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

Diese Deklaration entspricht folgendem:

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

Die Barrierefreiheitsdomäne des Typs der Konstante muss mit oder eine Obermenge der Barrierefreiheitsdomäne der Konstante selbst übereinstimmen. Der Konstantenausdruck muss einen Wert des Typs oder eines Typs zurückgeben, der implizit in den Typ der Konstante konvertierbar ist. Der konstante Ausdruck darf nicht zirkulär sein; d. h. eine Konstante kann nicht in Sich selbst definiert werden.

Der Compiler wertet die Konstantendeklarationen automatisch in der entsprechenden Reihenfolge aus. Im folgenden Beispiel wertet Yder Compiler zunächst die ZXWerte 10, 11 und 12 aus.

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

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

Wenn ein symbolischer Name für einen Konstantenwert gewünscht wird, aber der Typ des Werts in einer Konstantendeklaration nicht zulässig ist oder wenn der Wert nicht zur Kompilierungszeit durch einen Konstantenausdruck berechnet werden kann, kann stattdessen eine schreibgeschützte Variable verwendet werden.

Instanz- und freigegebene Variablen

Eine Instanz oder freigegebene Variable ist ein Mitglied eines Typs, der Informationen speichern kann.

VariableMemberDeclaration
    : Attributes? VariableModifier+ VariableDeclarators StatementTerminator
    ;

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

VariableDeclarators
    : VariableDeclarator ( Comma VariableDeclarator )*
    ;

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

VariableIdentifiers
    : VariableIdentifier ( Comma VariableIdentifier )*
    ;

VariableIdentifier
    : Identifier IdentifierModifiers
    ;

Der Dim Modifizierer muss angegeben werden, wenn keine Modifizierer angegeben werden, andernfalls jedoch nicht angegeben werden. Eine einzelne Variablendeklaration kann mehrere Variablendeklaratoren enthalten; Jeder Variablen-Deklarator führt eine neue Instanz oder ein freigegebenes Mitglied ein.

Wenn ein Initialisierer angegeben wird, kann nur eine Instanz oder freigegebene Variable vom Variablendeklarationsgeber deklariert werden:

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

Diese Einschränkung gilt nicht für Objektinitialisierer:

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

Eine variable, die mit dem Shared Modifizierer deklariert wird, ist eine freigegebene Variable. Eine freigegebene Variable identifiziert unabhängig von der Anzahl der Instanzen des erstellten Typs genau einen Speicherort. Eine freigegebene Variable tritt ein, wenn ein Programm mit der Ausführung beginnt und nicht mehr vorhanden ist, wenn das Programm beendet wird.

Eine freigegebene Variable wird nur für Instanzen eines bestimmten geschlossenen generischen Typs freigegeben. Beispiel:

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

    Public Sub New()  
        InstanceCount += 1 
    End Sub

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

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

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

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

Druckt aus:

1
1
2

Eine Variable, die ohne den Shared Modifizierer deklariert wird, wird als Instanzvariable bezeichnet. Jede Instanz einer Klasse enthält eine separate Kopie aller Instanzvariablen der Klasse. Eine Instanzvariable eines Verweistyps tritt ein, wenn eine neue Instanz dieses Typs erstellt wird und nicht mehr vorhanden ist, wenn keine Verweise auf diese Instanz vorhanden sind und die Finalize Methode ausgeführt wurde. Eine Instanzvariable eines Werttyps hat genau dieselbe Lebensdauer wie die Variable, zu der sie gehört. Anders ausgedrückt: Wenn eine Variable eines Werttyps ins Vorhandensein kommt oder nicht mehr vorhanden ist, geschieht die Instanzvariable des Werttyps.

Wenn der Deklarator eine As Klausel enthält, gibt die Klausel den Typ der von der Deklaration eingeführten Member an. Wenn der Typ ausgelassen wird und strenge Semantik verwendet wird, tritt ein Kompilierungszeitfehler auf. Andernfalls ist der Typ der Member implizit Object oder der Typ des Elementtypzeichens.

Hinweis: In der Syntax gibt es keine Mehrdeutigkeit: Wenn ein Deklarator einen Typ ausgelassen, wird immer der Typ eines folgenden Deklarators verwendet.

Die Barrierefreiheitsdomäne eines Instanz- oder freigegebenen Variablentyps oder Arrayelementtyps muss mit oder eine Obermenge der Barrierefreiheitsdomäne der Instanz oder der freigegebenen Variablen selbst identisch sein.

Das folgende Beispiel zeigt eine Color Klasse mit internen Instanzvariablen namens redPart, und bluePartgreenPart:

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

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

Read-Only Variablen

Wenn eine Instanz oder eine freigegebene Variablendeklaration einen ReadOnly Modifizierer enthält, können Zuordnungen zu den von der Deklaration eingeführten Variablen nur als Teil der Deklaration oder in einem Konstruktor in derselben Klasse auftreten. Insbesondere sind Zuordnungen zu einer schreibgeschützten Instanz oder einer freigegebenen Variablen nur in den folgenden Situationen zulässig:

  • In der Variablendeklaration, die die Instanz oder freigegebene Variable einführt (durch Einschließen eines Variableninitialisierers in die Deklaration).

  • Bei einer Instanzvariablen in den Instanzkonstruktoren der Klasse, die die Variabledeklaration enthält. Auf die Instanzvariable kann nur auf nicht qualifizierte Weise oder über oder durch Me zugegriffen MyClasswerden.

  • Für eine freigegebene Variable, im freigegebenen Konstruktor der Klasse, die die Deklaration der freigegebenen Variablen enthält.

Eine freigegebene schreibgeschützte Variable ist nützlich, wenn ein symbolischer Name für einen Konstantenwert gewünscht wird, aber wenn der Typ des Werts in einer Konstantendeklaration nicht zulässig ist oder der Wert nicht zur Kompilierungszeit durch einen Konstantenausdruck berechnet werden kann.

Ein Beispiel für die erste solche Anwendung folgt, in der freigegebene Farbvariablen deklariert ReadOnly werden, um zu verhindern, dass sie von anderen Programmen geändert werden:

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

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

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

Konstanten und schreibgeschützte freigegebene Variablen weisen unterschiedliche Semantik auf. Wenn ein Ausdruck auf eine Konstante verweist, wird der Wert der Konstante zur Kompilierungszeit abgerufen, aber wenn ein Ausdruck auf eine schreibgeschützte freigegebene Variable verweist, wird der Wert der freigegebenen Variablen erst während der Laufzeit abgerufen. Betrachten Sie die folgende Anwendung, die aus zwei separaten Programmen besteht.

file1.vb:

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

file2.vb:

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

Die Namespaces Program1 und Program2 kennzeichnen zwei Programme, die separat kompiliert werden. Da variable Program1.Utils.X als Shared ReadOnlydeklariert wird, wird die Wertausgabe der Console.WriteLine Anweisung zur Kompilierungszeit nicht bekannt, sondern zur Laufzeit abgerufen. Wenn der Wert geändert X und Program1 neu kompiliert wird, gibt die Console.WriteLine Anweisung den neuen Wert auch dann aus, wenn Program2 er nicht neu kompiliert wird. Wenn X es sich jedoch um eine Konstante handelte, wäre der Wert zum X Zeitpunkt Program2 der Kompilierung erhalten worden und wäre von Änderungen Program1Program2 bis zur Neukompilierung unberührt geblieben.

WithEvents-Variablen

Ein Typ kann deklarieren, dass er eine Reihe von Ereignissen behandelt, die von einer seiner Instanzen oder freigegebenen Variablen ausgelöst werden, indem die Instanz oder freigegebene Variable deklariert wird, die die Ereignisse mit dem WithEvents Modifizierer auslöst. Beispiel:

Class Raiser
    Public Event E1()

    Public Sub Raise()
        RaiseEvent E1
    End Sub
End Class

Module Test
    Private WithEvents x As Raiser

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

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

In diesem Beispiel behandelt die Methode E1Handler das Ereignis E1 , das von der Instanz des Typs Raiser ausgelöst wird, der in der Instanzvariable xgespeichert ist.

Der WithEvents Modifizierer bewirkt, dass die Variable mit einem führenden Unterstrich umbenannt und durch eine Eigenschaft desselben Namens ersetzt wird, der den Ereignis-Hookup durchführt. Wenn der Name der Variablen beispielsweise lautetF_F, wird sie umbenannt und eine Eigenschaft F implizit deklariert. Wenn ein Konflikt zwischen dem neuen Namen der Variablen und einer anderen Deklaration vorliegt, wird ein Kompilierungsfehler gemeldet. Alle Attribute, die auf die Variable angewendet werden, werden an die umbenannte Variable übertragen.

Die implizite Eigenschaft, die von einer WithEvents Deklaration erstellt wird, übernimmt das Hooking und Aufheben desHookings der relevanten Ereignishandler. Wenn der Variablen ein Wert zugewiesen wird, ruft die Eigenschaft zuerst die remove Methode für das Ereignis für die Instanz auf, die sich derzeit in der Variablen befindet (lösen Sie ggf. den Hooking des vorhandenen Ereignishandlers auf). Als Nächstes wird die Zuweisung ausgeführt, und die Eigenschaft ruft die add Methode für das Ereignis für die neue Instanz in der Variablen auf (verbinden den neuen Ereignishandler). Der folgende Code entspricht dem obigen Code für das Standardmodul Test:

Module Test
    Private _x As Raiser

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

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

            ' Change value.
            _x = Value

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

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

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

Es ist ungültig, eine Instanz oder freigegebene Variable zu deklarieren, als WithEvents ob die Variable als Struktur eingegeben wird. Darüber hinaus WithEvents dürfen sie nicht in einer Struktur angegeben werden und WithEventsReadOnly können nicht kombiniert werden.

Variable Initialisierer

Instanzen- und freigegebene Variablendeklarationen in Klassen und Instanzvariablendeklarationen (aber keine freigegebenen Variablendeklarationen) in Strukturen können Variableninitialisierer enthalten. Bei Shared Variablen entsprechen Variableninitialisierer Zuweisungsanweisungen, die nach beginn des Programms ausgeführt werden, aber bevor zuerst auf die Shared Variable verwiesen wird. Variablen entsprechen z. B. Variableninitialisierern Zuordnungsanweisungen, die beim Erstellen einer Instanz der Klasse ausgeführt werden. Strukturen können keine Instanzvariableninitialisierer haben, da ihre parameterlosen Konstruktoren nicht geändert werden können.

Betrachten Sie das folgenden Beispiel:

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

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

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

Das Beispiel erzeugt die folgende Ausgabe:

x = 1.4142135623731, i = 100, s = Hello

Eine Aufgabe, die x ausgeführt wird, wenn die Klasse geladen wird, und Zuweisungen zu i und s auftreten, wenn eine neue Instanz der Klasse erstellt wird.

Es ist nützlich, variable Initialisierer als Zuordnungsanweisungen zu betrachten, die automatisch in den Block des Konstruktors des Typs eingefügt werden. Das folgende Beispiel enthält mehrere Instanzvariableninitialisierer.

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

    Public Sub New()
        count = 0
    End Sub

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

Class B
    Inherits A

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

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

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

Das Beispiel entspricht dem unten gezeigten Code, wobei jeder Kommentar eine automatisch eingefügte Anweisung angibt.

Class A
    Private x, y, count As Integer

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

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

Class B
    Inherits A

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

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

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

Alle Variablen werden vor der Ausführung variabler Initialisierer auf den Standardwert ihres Typs initialisiert. Beispiel:

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

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

Da b der Standardwert beim Laden der Klasse automatisch initialisiert wird und i automatisch auf den Standardwert initialisiert wird, wenn eine Instanz der Klasse erstellt wird, erzeugt der vorangehende Code die folgende Ausgabe:

b = False, i = 0

Jeder Variableninitialisierer muss einen Wert des Variablentyps oder eines Typs zurückgeben, der implizit in den Typ der Variablen konvertierbar ist. Ein Variableinitialisierer kann zirkulär sein oder auf eine Variable verweisen, die danach initialisiert wird. In diesem Fall ist der Wert der Referenzvariablen der Standardwert für die Zwecke des Initialisierers. Ein solcher Initialisierer ist von dubiosen Werten.

Es gibt drei Formen von Variableninitialisierern: reguläre Initialisierungen, Arraygrößeninitialisierer und Objektinitialisierer. Die ersten beiden Formulare werden nach einem Gleichheitszeichen angezeigt, das auf den Typnamen folgt, die beiden letzteren sind Teil der Deklaration selbst. Für eine bestimmte Deklaration kann nur eine Form des Initialisierers verwendet werden.

Reguläre Initialisierer

Ein regulärer Initialisierer ist ein Ausdruck, der implizit in den Typ der Variablen konvertierbar ist. Es wird nach einem Gleichheitszeichen angezeigt, das dem Typnamen folgt und als Wert klassifiziert werden muss. Beispiel:

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

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

Dieses Programm erzeugt die Ausgabe:

x = 10, y = 20

Wenn eine Variabledeklaration über einen regulären Initialisierer verfügt, kann jeweils nur eine einzelne Variable deklariert werden. Beispiel:

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

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

Objektinitialisierer

Ein Objektinitialisierer wird mithilfe eines Objekterstellungsausdrucks anstelle des Typnamens angegeben. Ein Objektinitialisierer entspricht einem regulären Initialisierer, der das Ergebnis des Objekterstellungsausdrucks der Variablen zuweist. So

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

entspricht

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

Die Klammer in einem Objektinitialisierer wird immer als Argumentliste für den Konstruktor und nie als Arraytypmodifizierer interpretiert. Ein Variablenname mit einem Objektinitialisierer kann keinen Arraytypmodifizierer oder einen Null-Modifizierer aufweisen.

Array-Size Initialisierer

Ein Initialisierer mit Arraygröße ist ein Modifizierer für den Namen der Variablen, der eine Gruppe von Bemaßungsgrenzen angibt, die durch Ausdrücke gekennzeichnet sind.

ArraySizeInitializationModifier
    : OpenParenthesis BoundList CloseParenthesis ArrayTypeModifiers?
    ;

BoundList
    : Bound ( Comma Bound )*
    ;

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

Die oberen Gebundenen Ausdrücke müssen als Werte klassifiziert werden und müssen implizit in Integer. Der Satz der oberen Grenzen entspricht einem variablen Initialisierer eines Arrayerstellungsausdrucks mit den angegebenen oberen Begrenzungen. Die Anzahl der Dimensionen des Arraytyps wird aus dem Arraygrößeninitialisierer abgeleitet. So

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

entspricht

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

Alle oberen Begrenzungen müssen gleich oder größer als -1 sein, und alle Dimensionen müssen eine obere Grenze angegeben haben. Wenn der Elementtyp des Arrays, das initialisiert wird, selbst ein Arraytyp ist, wechseln die Modifizierer des Arraytyps rechts vom Initialisierer der Arraygröße. Beispiel:

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

deklariert eine lokale Variable x , deren Typ ein zweidimensionales Array von dreidimensionalen Arrays ist Integer, initialisiert für ein Array mit Grenzen 0..5 in der ersten Dimension und 0..10 in der zweiten Dimension. Es ist nicht möglich, einen Arraygrößeninitialisierer zu verwenden, um die Elemente einer Variablen zu initialisieren, deren Typ ein Array von Arrays ist.

Eine Variabledeklaration mit einem Arraygrößeninitialisierer kann keinen Arraytypmodifizierer für den Typ oder einen regulären Initialisierer aufweisen.

System.MarshalByRefObject-Klassen

Klassen, die von der Klasse System.MarshalByRefObject abgeleitet werden, werden über Kontextgrenzen hinweg mithilfe von Proxys (d. a. durch Verweis) gemarstet, anstatt durch Kopieren (d. s. nach Wert). Dies bedeutet, dass eine Instanz einer solchen Klasse möglicherweise keine echte Instanz ist, sondern nur ein Stub sein kann, der variable Zugriffe und Methodenaufrufe über eine Kontextgrenze hinweg marshallt.

Daher ist es nicht möglich, einen Verweis auf den Speicherort von Variablen zu erstellen, die für solche Klassen definiert sind. Dies bedeutet, dass variablen, von denen abgeleitete System.MarshalByRefObject Klassen nicht an Referenzparameter übergeben werden können, und Methoden und Variablen von Variablen, die als Werttypen eingegeben werden, können nicht zugegriffen werden. Stattdessen behandelt Visual Basic Für solche Klassen definierte Variablen, z. B. Eigenschaften (da die Einschränkungen für Eigenschaften identisch sind).

Es gibt eine Ausnahme zu dieser Regel: Ein Element, das Me implizit oder explizit qualifiziert ist, ist von den oben genannten Einschränkungen ausgenommen, da Me immer garantiert ein tatsächliches Objekt und kein Proxy ist.

Eigenschaften

Eigenschaften sind eine natürliche Erweiterung von Variablen; beide sind benannte Member mit zugeordneten Typen, und die Syntax für den Zugriff auf Variablen und Eigenschaften ist identisch. Im Gegensatz zu Variablen bezeichnen Eigenschaften jedoch keine Speicherorte. Stattdessen verfügen Eigenschaften über Accessoren, die die auszuführenden Anweisungen angeben, um ihre Werte zu lesen oder zu schreiben.

Eigenschaften werden mit Eigenschaftendeklarationen definiert. Der erste Teil einer Eigenschaftsdeklaration ähnelt einer Felddeklaration. Der zweite Teil enthält einen Get Accessor und/oder einen Set Accessor.

PropertyMemberDeclaration
    : RegularPropertyMemberDeclaration
    | MustOverridePropertyMemberDeclaration
    | AutoPropertyMemberDeclaration
    ;

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

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

MustOverridePropertyMemberDeclaration
    : Attributes? MustOverridePropertyModifier+ PropertySignature
      ImplementsClause? StatementTerminator
    ;

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

InterfacePropertyMemberDeclaration
    : Attributes? InterfacePropertyModifier* PropertySignature StatementTerminator
    ;

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

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

MustOverridePropertyModifier
    : PropertyModifier
    | 'MustOverride'
    ;

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

PropertyAccessorDeclaration
    : PropertyGetDeclaration
    | PropertySetDeclaration
    ;

Im folgenden Beispiel definiert die Button Klasse eine Caption Eigenschaft.

Public Class Button
    Private captionValue As String

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

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

    ...
End Class

Basierend auf der Button obigen Klasse ist Folgendes ein Beispiel für die Verwendung der Caption Eigenschaft:

Dim okButton As Button = New Button()

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

Hier wird der Set Accessor aufgerufen, indem der Eigenschaft ein Wert zugewiesen wird, und der Get Accessor wird aufgerufen, indem auf die Eigenschaft in einem Ausdruck verwiesen wird.

Wenn kein Typ für eine Eigenschaft angegeben wird und strenge Semantik verwendet wird, tritt ein Kompilierungszeitfehler auf; andernfalls ist der Typ der Eigenschaft implizit Object oder der Typ des Typs der Eigenschaft. Eine Eigenschaftsdeklaration kann entweder einen Get Accessor enthalten, der den Wert der Eigenschaft, einen Set Accessor abruft, der den Wert der Eigenschaft speichert, oder beides. Da eine Eigenschaft Methoden implizit deklariert, kann eine Eigenschaft mit denselben Modifizierern wie eine Methode deklariert werden. Wenn die Eigenschaft in einer Schnittstelle definiert oder mit dem MustOverride Modifizierer definiert ist, muss der Eigenschaftentext und das End Konstrukt weggelassen werden. Andernfalls tritt ein Kompilierungszeitfehler auf.

Die Indexparameterliste besteht aus der Signatur der Eigenschaft, sodass Eigenschaften möglicherweise für Indexparameter, aber nicht für den Typ der Eigenschaft überladen werden können. Die Indexparameterliste ist identisch mit einer regulären Methode. Allerdings kann keines der Parameter mit dem ByRef Modifizierer geändert werden, und keiner davon kann benannt Value werden (der für den impliziten Wertparameter im Set Accessor reserviert ist).

Eine Eigenschaft kann wie folgt deklariert werden:

  • Wenn die Eigenschaft keinen Eigenschaftstypmodifizierer angibt, muss die Eigenschaft über einen Get Accessor und einen Set Accessor verfügen. Die Eigenschaft wird als Lese-/Schreibzugriffseigenschaft bezeichnet.

  • Wenn die Eigenschaft den ReadOnly Modifizierer angibt, muss die Eigenschaft über einen Get Accessor verfügen und möglicherweise keinen Accessor haben Set . Die Eigenschaft wird als schreibgeschützte Eigenschaft bezeichnet. Es ist ein Kompilierzeitfehler, dass eine schreibgeschützte Eigenschaft das Ziel einer Zuweisung ist.

  • Wenn die Eigenschaft den WriteOnly Modifizierer angibt, muss die Eigenschaft über einen Set Accessor verfügen und möglicherweise keinen Accessor haben Get . Die Eigenschaft wird als schreibgeschützte Eigenschaft bezeichnet. Es handelt sich um einen Kompilierungsfehler, um auf eine schreibgeschützte Eigenschaft in einem Ausdruck zu verweisen, mit Ausnahme des Ziels einer Zuordnung oder als Argument für eine Methode.

Set Die Get Accessoren einer Eigenschaft sind keine unterschiedlichen Member, und es ist nicht möglich, die Accessoren einer Eigenschaft separat zu deklarieren. Im folgenden Beispiel wird keine einzelne Lese-/Schreibzugriffseigenschaft deklariert. Stattdessen werden zwei Eigenschaften mit demselben Namen deklariert: schreibgeschützt und schreibgeschützt:

Class A
    Private nameValue As String

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

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

Da zwei Elemente, die in derselben Klasse deklariert sind, nicht denselben Namen aufweisen können, verursacht das Beispiel einen Kompilierungszeitfehler.

Standardmäßig ist die Barrierefreiheit von Eigenschaften Get und Set Accessoren identisch mit der Barrierefreiheit der Eigenschaft selbst. Die Zugriffstasten und Set Accessoren Get können die Barrierefreiheit jedoch auch separat von der Eigenschaft angeben. In diesem Fall muss die Barrierefreiheit eines Accessors restriktiver sein als die Barrierefreiheit der Eigenschaft, und nur ein Accessor kann eine andere Barrierefreiheitsstufe als die Eigenschaft aufweisen. Zugriffstypen gelten als mehr oder weniger restriktiv:

  • Private ist restriktiver als Public, Protected Friend, , Protectedoder Friend.

  • Friend ist restriktiver als Protected Friend oder Public.

  • Protected ist restriktiver als Protected Friend oder Public.

  • Protected Friend ist restriktiver als Public.

Wenn auf eine der Accessoren einer Eigenschaft zugegriffen werden kann, die andere jedoch nicht, wird die Eigenschaft so behandelt, als wäre sie schreibgeschützt oder schreibgeschützt. Beispiel:

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

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

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

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

Wenn ein abgeleiteter Typ eine Eigenschaft schattiert, blendet die abgeleitete Eigenschaft die schattierte Eigenschaft im Hinblick auf Das Lesen und Schreiben aus. Im folgenden Beispiel blendet die Eigenschaft in B Bezug auf Lesen und Schreiben die PP Eigenschaft A aus:

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

Class B
    Inherits A

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

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

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

Die Barrierefreiheitsdomäne der Rückgabetyp- oder Parametertypen muss mit der Barrierefreiheitsdomäne der Eigenschaft selbst identisch sein oder eine Obermenge der Barrierefreiheitsdomäne der Eigenschaft selbst sein. Eine Eigenschaft darf nur über einen Set Accessor und einen Get Accessor verfügen.

Mit Ausnahme von Unterschieden bei der Deklarations- und Aufrufssyntax verhalten sich genau OverridableMustInheritNotOverridableOverridesMustOverridewie Overridable, , NotOverridable, , Overridesund MustInheritMustOverrideMethoden. Wenn eine Eigenschaft außer Kraft gesetzt wird, muss die Überschreibungseigenschaft denselben Typ aufweisen (schreibgeschützt, schreibgeschützt, schreibgeschützt). Eine Overridable Eigenschaft kann keinen Accessor enthalten Private .

Im folgenden Beispiel X handelt es sich um eine Overridable schreibgeschützte Eigenschaft, Y eine Overridable Lese-/Schreibzugriffseigenschaft und Z eine MustOverride Lese-/Schreibzugriffseigenschaft.

MustInherit Class A
    Private _y As Integer

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

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

    Public MustOverride Property Z() As Integer
End Class

Da Z ist MustOverride, muss die enthaltende Klasse A deklariert MustInheritwerden.

Im Gegensatz dazu wird eine Klasse angezeigt, die von der Klasse A abgeleitet wird:

Class B
    Inherits A

    Private _z As Integer

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

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

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

Hier werden die Deklarationen von Eigenschaften XYund Z die Basiseigenschaften überschreiben. Jede Eigenschaftsdeklaration stimmt exakt mit den Zugriffsmodifizierern, dem Typ und dem Namen der entsprechenden geerbten Eigenschaft überein. Der Get Accessor der Eigenschaft X und der Set Accessor der Eigenschaft Y verwenden das MyBase Schlüsselwort für den Zugriff auf die geerbten Eigenschaften. Die Deklaration der Eigenschaft Z überschreibt die MustOverride Eigenschaft - daher gibt es keine herausragenden MustOverride Member in der Klasse Bund B darf eine normale Klasse sein.

Eigenschaften können verwendet werden, um die Initialisierung einer Ressource bis zu dem Zeitpunkt zu verzögern, an dem sie zuerst referenziert wird. Beispiel:

Imports System.IO

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

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

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

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

Die ConsoleStreams Klasse enthält drei Eigenschaften, In, Outund Error, die die Standardeingabe, Ausgabe und Fehlergeräte darstellen. Indem diese Member als Eigenschaften zugänglich gemacht werden, kann die ConsoleStreams Klasse die Initialisierung verzögern, bis sie tatsächlich verwendet werden. Wenn Sie beispielsweise zuerst auf die Out Eigenschaft verweisen, wie im Folgenden ConsoleStreams.Out.WriteLine("hello, world")dargestellt, wird der zugrunde liegende TextWriter Wert für das Ausgabegerät initialisiert. Wenn die Anwendung jedoch keinen Verweis auf die In Und-Eigenschaften Error macht, werden keine Objekte für diese Geräte erstellt.

Abrufen von Accessordeklarationen

Ein Get Accessor (Getter) wird mithilfe einer Eigenschaftsdeklaration Get deklariert. Eine Eigenschaftsdeklaration Get besteht aus dem Schlüsselwort Get , gefolgt von einem Anweisungsblock. Aufgrund einer Eigenschaft mit dem Namen Pdeklariert eine Get Accessordeklaration implizit eine Methode mit dem Namen get_P mit denselben Modifizierern, Typ und Parameterliste wie die Eigenschaft. Wenn der Typ eine Deklaration mit diesem Namen enthält, ergibt sich ein Kompilierungszeitfehler, die implizite Deklaration wird jedoch für die Zwecke der Namensbindung ignoriert.

Eine spezielle lokale Variable, die implizit im Get Deklarationsbereich des Accessortexts mit demselben Namen wie die Eigenschaft deklariert wird, stellt den Rückgabewert der Eigenschaft dar. Die lokale Variable verfügt über spezielle Namensauflösungssemantik, wenn sie in Ausdrücken verwendet wird. Wenn die lokale Variable in einem Kontext verwendet wird, der einen Ausdruck erwartet, der als Methodengruppe klassifiziert wird, z. B. ein Aufrufausdruck, wird der Name in die Funktion und nicht in die lokale Variable aufgelöst. Beispiel:

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

Die Verwendung von Klammern kann mehrdeutige Situationen verursachen (z F(1) . B. wenn F es sich um eine Eigenschaft handelt, deren Typ ein eindimensionales Array ist). In allen mehrdeutigen Situationen wird der Name in die Eigenschaft und nicht in die lokale Variable aufgelöst. Beispiel:

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

Wenn der Steuerfluss den Accessortext Get verlässt, wird der Wert der lokalen Variablen an den Aufrufausdruck übergeben. Da das Aufrufen eines Get Accessors konzeptionell dem Lesen des Werts einer Variablen entspricht, wird er als ungültiger Programmierstil für Get Accessoren angesehen, um erkennbare Nebenwirkungen zu haben, wie im folgenden Beispiel veranschaulicht:

Class Counter
    Private Value As Integer

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

Der Wert der NextValue Eigenschaft hängt davon ab, wie oft auf die Eigenschaft zuvor zugegriffen wurde. Daher erzeugt der Zugriff auf die Eigenschaft einen feststellbaren Nebeneffekt, und die Eigenschaft sollte stattdessen als Methode implementiert werden.

Die Konvention "keine Nebenwirkungen" für Get Accessoren bedeutet nicht, dass Get Accessoren immer in einfache Rückgabewerte geschrieben werden sollten, die in Variablen gespeichert sind. In der Tat Get berechnen Accessoren häufig den Wert einer Eigenschaft, indem sie auf mehrere Variablen zugreifen oder Methoden aufrufen. Ein ordnungsgemäß entworfener Get Accessor führt jedoch keine Aktionen aus, die zu feststellbaren Änderungen im Zustand des Objekts führen.

Hinweis: Get Accessoren haben die gleiche Einschränkung für die Zeilenplatzierung, die Unterroutinen aufweisen. Die Anfangsanweisung, end-Anweisung und der Block müssen alle am Anfang einer logischen Zeile angezeigt werden.

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

Festlegen von Accessordeklarationen

Ein Set Accessor (Setter) wird mithilfe einer Eigenschaftssatzdeklaration deklariert. Eine Eigenschaftensatzdeklaration besteht aus dem Schlüsselwort Set, einer optionalen Parameterliste und einem Anweisungsblock. Aufgrund einer Eigenschaft mit dem Namen Pdeklariert eine Setterdeklaration implizit eine Methode mit dem Namen set_P mit denselben Modifizierern und der Parameterliste wie die Eigenschaft. Wenn der Typ eine Deklaration mit diesem Namen enthält, ergibt sich ein Kompilierungszeitfehler, die implizite Deklaration wird jedoch für die Zwecke der Namensbindung ignoriert.

Wenn eine Parameterliste angegeben ist, muss ein Element vorhanden sein, dieses Element darf keine Modifizierer haben, außer ByVal, und der Typ muss mit dem Typ der Eigenschaft identisch sein. Der Parameter stellt den festzulegenden Eigenschaftswert dar. Wenn der Parameter nicht angegeben wird, wird ein benannter Value Parameter implizit deklariert.

Hinweis: Set Accessoren haben die gleiche Einschränkung für die Zeilenplatzierung, die Unterroutinen aufweisen. Die Anfangsanweisung, end-Anweisung und der Block müssen alle am Anfang einer logischen Zeile angezeigt werden.

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

Standardeigenschaften

Eine Eigenschaft, die den Modifizierer Default angibt, wird als Standardeigenschaft bezeichnet. Jeder Typ, der Eigenschaften zulässt, verfügt möglicherweise über eine Standardeigenschaft, einschließlich Schnittstellen. Auf die Standardeigenschaft kann verwiesen werden, ohne die Instanz mit dem Namen der Eigenschaft qualifizieren zu müssen. So erhält man eine Klasse

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

der Code

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

        y = x(10)
    End Sub
End Module

entspricht

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

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

Sobald eine Eigenschaft deklariert Defaultwurde, werden alle Eigenschaften, die für diesen Namen in der Vererbungshierarchie überladen sind, zur Standardeigenschaft, unabhängig davon, ob sie deklariert Default wurden oder nicht. Das Deklarieren einer Eigenschaft Default in einer abgeleiteten Klasse, wenn die Basisklasse eine Standardeigenschaft durch einen anderen Namen deklariert hat, erfordert keine anderen Modifizierer, Shadows z. B. oder Overrides. Dies liegt daran, dass die Standardeigenschaft keine Identität oder Signatur aufweist und daher nicht schattiert oder überladen werden kann. Beispiel:

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

Class Derived
    Inherits Base

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

Class MoreDerived
    Inherits Derived

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

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

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

Dieses Programm erzeugt die Ausgabe:

MoreDerived = 10
Derived = 10
Base = 10

Alle in einem Typ deklarierten Standardeigenschaften müssen denselben Namen haben und müssen den Modifizierer angeben Default . Da eine Standardeigenschaft ohne Indexparameter zu einer mehrdeutigen Situation führen würde, wenn Instanzen der enthaltenden Klasse zugewiesen werden, müssen Standardeigenschaften Indexparameter aufweisen. Wenn eine Eigenschaft, die für einen bestimmten Namen überladen ist, den Default Modifizierer enthält, müssen alle eigenschaften, die für diesen Namen überladen sind, diese angeben. Standardeigenschaften dürfen nicht sein Shared, und mindestens ein Accessor der Eigenschaft darf nicht sein Private.

Automatisch implementierte Eigenschaften

Wenn eine Eigenschaft keine Deklaration von Accessoren ausgelassen, wird automatisch eine Implementierung der Eigenschaft bereitgestellt, es sei denn, die Eigenschaft wird in einer Schnittstelle deklariert oder deklariert MustOverride. Nur Lese-/Schreibeigenschaften ohne Argumente können automatisch implementiert werden. andernfalls tritt ein Kompilierungszeitfehler auf.

Eine automatisch implementierte Eigenschaft x, auch eine überschreibende andere Eigenschaft, führt eine private lokale Variable _x mit demselben Typ wie die Eigenschaft ein. Wenn zwischen dem Namen der lokalen Variablen und einer anderen Deklaration ein Konflikt auftritt, wird ein Kompilierungszeitfehler gemeldet. Der Accessor der automatisch implementierten Eigenschaft Get gibt den Wert des lokalen Zugriffsmoduls Set und des Accessors der Eigenschaft zurück, der den Wert der lokalen Eigenschaft festlegt. Beispielsweise die Deklaration:

Public Property x() As Integer

ist ungefähr gleichbedeutend mit:

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

Wie bei Variablendeklarationen kann eine automatisch implementierte Eigenschaft einen Initialisierer enthalten. Beispiel:

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

Hinweis: Wenn eine automatisch implementierte Eigenschaft initialisiert wird, wird sie über die Eigenschaft und nicht über das zugrunde liegende Feld initialisiert. Dies ist so, dass überschriebene Eigenschaften die Initialisierung abfangen können, wenn sie erforderlich sind.

Arrayinitialisierer sind für automatisch implementierte Eigenschaften zulässig, es sei denn, es gibt keine Möglichkeit, die Arraygrenzen explizit anzugeben. Beispiel:

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

' Invalid
Property x4(5) As Short

Iteratoreigenschaften

Eine Iteratoreigenschaft ist eine Eigenschaft mit dem Iterator Modifizierer. Es wird aus demselben Grund verwendet, dass eine Iteratormethode (Section Iterator Methods) verwendet wird - als bequeme Methode zum Generieren einer Sequenz, die von der For Each Anweisung verwendet werden kann. Der Get Accessor einer Iteratoreigenschaft wird auf die gleiche Weise interpretiert wie eine Iteratormethode.

Eine Iteratoreigenschaft muss über einen expliziten Get Accessor verfügen, und sein Typ muss IEnumerator, oder IEnumerable(Of T)IEnumerableIEnumerator(Of T) oder für einige .T

Hier ist ein Beispiel für eine Iteratoreigenschaft:

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

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

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

Betriebspersonal

Operatoren sind Methoden, die die Bedeutung eines vorhandenen Visual Basic-Operators für die enthaltende Klasse definieren. Wenn der Operator auf die Klasse in einem Ausdruck angewendet wird, wird der Operator in einen Aufruf der Operatormethode kompiliert, die in der Klasse definiert ist. Das Definieren eines Operators für eine Klasse wird auch als Überladung des Operators bezeichnet.

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

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

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

Es ist nicht möglich, einen bereits vorhandenen Operator zu überladen; in der Praxis gilt dies in erster Linie für Konvertierungsoperatoren. Beispielsweise ist es nicht möglich, die Konvertierung von einer abgeleiteten Klasse in eine Basisklasse zu überladen:

Class Base
End Class

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

Operatoren können auch im Allgemeinen des Worts überladen werden:

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

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

Operatordeklarationen fügen dem Deklarationsbereich des typs keine expliziten Namen hinzu; Sie deklarieren jedoch implizit eine entsprechende Methode, die mit den Zeichen "op_" beginnt. In den folgenden Abschnitten werden die entsprechenden Methodennamen mit jedem Operator aufgelistet.

Es gibt drei Klassen von Operatoren, die definiert werden können: unäre Operatoren, binäre Operatoren und Konvertierungsoperatoren. Alle Operatordeklarationen teilen bestimmte Einschränkungen:

  • Operatordeklarationen müssen immer und PublicShared. Der Public Modifizierer kann in Kontexten weggelassen werden, in denen der Modifizierer angenommen wird.

  • Die Parameter eines Operators können nicht deklariert ByRefwerden oder OptionalParamArray.

  • Der Typ mindestens eines der Operanden oder des Rückgabewerts muss der Typ sein, der den Operator enthält.

  • Für Operatoren ist keine Funktionsrücklaufvariable definiert. Daher muss die Return Anweisung verwendet werden, um Werte aus einem Operatortext zurückzugeben.

Die einzige Ausnahme für diese Einschränkungen gilt für Nullwertetypen. Da Nullwertetypen nicht über eine tatsächliche Typdefinition verfügen, kann ein Werttyp benutzerdefinierte Operatoren für die nullable Version des Typs deklarieren. Bei der Bestimmung, ob ein Typ einen bestimmten benutzerdefinierten Operator deklarieren kann, werden die ? Modifizierer zunächst von allen Typen entfernt, die für die Gültigkeitsprüfung an der Deklaration beteiligt sind. Diese Entspannung gilt nicht für den Rückgabetyp der und der IsTrue Operatoren; sie müssen immer noch zurückgegeben Booleanwerden, nicht Boolean?.IsFalse

Die Rangfolge und Zuordnung eines Operators können nicht durch eine Operatordeklaration geändert werden.

Hinweis: Operatoren haben die gleiche Einschränkung für die Zeilenplatzierung, die Unterroutinen aufweisen. Die Anfangsanweisung, end-Anweisung und der Block müssen alle am Anfang einer logischen Zeile angezeigt werden.

Unäre Operatoren

Die folgenden unären Operatoren können überladen werden:

  • Der unäre Plusoperator + (entsprechende Methode: op_UnaryPlus)

  • Der unäre Minusoperator - (entsprechende Methode: op_UnaryNegation)

  • Der logische Not Operator (entsprechende Methode: op_OnesComplement)

  • Die und IsFalse die IsTrue Operatoren (entsprechende Methoden: op_True, op_False)

Alle überladenen unären Operatoren müssen einen einzelnen Parameter des enthaltenden Typs verwenden und können jeden Typ zurückgeben, mit Ausnahme IsTrue von und IsFalse, die zurückgegeben Booleanwerden muss. Wenn der enthaltende Typ ein generischer Typ ist, müssen die Typparameter mit den Typparametern des enthaltenden Typs übereinstimmen. Beispiel:

Structure Complex
    ...

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

Wenn ein Typ eine von IsTrue oder IsFalseoder , dann muss er auch den anderen überladen. Wenn nur eine überladen ist, ergibt sich ein Kompilierungszeitfehler.

Hinweis: IsTrue und IsFalse sind keine reservierten Wörter.

Binäre Operatoren

Die folgenden binären Operatoren können überladen werden:

  • Addition , Subtraktion , Multiplikation *, Division /, integrale Division \, Modulo Mod und Exponentiation ^ Operatoren (entsprechende Methode: op_Addition, , op_Divisionop_Subtractionop_Modulusop_IntegerDivisionop_Multiply) op_Exponent-+

  • Die relationalen Operatoren =, , <>, <, >, <=, ( >= entsprechende Methoden: op_Equality, , op_Inequality, op_LessThan, op_GreaterThan, , op_LessThanOrEqual). op_GreaterThanOrEqual Hinweis: Während der Gleichheitsoperator überladen werden kann, kann der Zuordnungsoperator (nur in Zuordnungsanweisungen verwendet) nicht überladen werden.

  • Der Like Operator (entsprechende Methode: op_Like)

  • Der Verkettungsoperator & (entsprechende Methode: op_Concatenate)

  • Die logischen OrAndUnd Xor Operatoren (entsprechende Methoden: op_BitwiseAnd, op_BitwiseOr, op_ExclusiveOr)

  • Die Schichtoperatoren << und >> (entsprechende Methoden: op_LeftShift, op_RightShift)

Alle überladenen binären Operatoren müssen den enthaltenden Typ als einen der Parameter verwenden. Wenn der enthaltende Typ ein generischer Typ ist, müssen die Typparameter mit den Typparametern des enthaltenden Typs übereinstimmen. Die Schichtoperatoren beschränken diese Regel weiter, um den ersten Parameter des enthaltenden Typs festzulegen; Der zweite Parameter muss immer vom Typ Integersein.

Die folgenden binären Operatoren müssen in Paaren deklariert werden:

  • Operator und Operator =<>

  • Operator und Operator ><

  • Operator und Operator >=<=

Wenn eines der Paare deklariert wird, muss die andere auch mit übereinstimmenden Parametern und Rückgabetypen deklariert werden, oder ein Kompilierungszeitfehler führt zu einem Fehler. (Hinweis. Der Zweck der Notwendigkeit gekoppelter Deklarationen relationaler Operatoren besteht darin, mindestens ein Mindestmaß an logischer Konsistenz in überladenen Operatoren sicherzustellen.)

Im Gegensatz zu den relationalen Operatoren wird die Überlastung sowohl der Abteilungs- als auch der integralen Abteilungsoperatoren dringend abgeraten, obwohl kein Fehler. (Hinweis. Im Allgemeinen sollten die beiden Arten von Divisionen vollständig voneinander unterschieden werden: ein Typ, der die Division unterstützt, ist entweder integral (in diesem Fall sollte er unterstützt \werden) oder nicht (in diesem Fall sollte er unterstützt werden /). Wir haben angenommen, dass es ein Fehler ist, beide Operatoren zu definieren, aber da ihre Sprachen in der Regel nicht zwischen zwei Arten von Division unterscheiden, wie Visual Basic funktioniert, war es sicher, die Übung zuzulassen, aber dringend davon abzuhalten.)

Zusammengesetzte Zuordnungsoperatoren können nicht direkt überladen werden. Wenn der entsprechende binäre Operator überladen ist, verwendet der Operator für die zusammengesetzte Zuordnung stattdessen den überladenen Operator. Beispiel:

Structure Complex
    ...

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

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

Konvertierungsoperatoren

Konvertierungsoperatoren definieren neue Konvertierungen zwischen Typen. Diese neuen Konvertierungen werden als benutzerdefinierte Konvertierungen bezeichnet. Ein Konvertierungsoperator konvertiert von einem Quelltyp, der durch den Parametertyp des Konvertierungsoperators angegeben ist, in einen Zieltyp, der durch den Rückgabetyp des Konvertierungsoperators angegeben wird. Konvertierungen müssen entweder als Verbreiterung oder Schmalung klassifiziert werden. Eine Konvertierungsoperatordeklaration, die das Widening Schlüsselwort enthält, führt eine benutzerdefinierte Erweiterungskonvertierung ein (entsprechende Methode: op_Implicit). Eine Konvertierungsoperatordeklaration, die das Narrowing Schlüsselwort enthält, führt eine benutzerdefinierte Schmalungskonvertierung ein (entsprechende Methode: op_Explicit).

Im Allgemeinen sollten benutzerdefinierte Verbreiterungskonvertierungen so konzipiert werden, dass keine Ausnahmen ausgelöst werden und niemals Informationen verloren gehen. Wenn eine benutzerdefinierte Konvertierung Ausnahmen verursachen kann (z. B. weil das Quellargument außerhalb des Zulässigen liegt) oder Verlust von Informationen (z. B. Verwerfen von Bits mit hoher Reihenfolge), sollte diese Konvertierung als schmale Konvertierung definiert werden. Im Beispiel:

Structure Digit
    Dim value As Byte

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

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

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

Die Konvertierung von Digit in Byte eine Erweiterung ist eine Erweiterungskonvertierung, da sie niemals Ausnahmen auslöst oder Informationen verliert, aber die Konvertierung von Byte in Digit eine schmale Konvertierung ist, da Digit nur eine Teilmenge der möglichen Werte eines .Byte

Im Gegensatz zu allen anderen Typenmembern, die überladen werden können, enthält die Signatur eines Konvertierungsoperators den Zieltyp der Konvertierung. Dies ist der einzige Typmemm, für den der Rückgabetyp an der Signatur teilnimmt. Die Verbreiterung oder Verengungsklassifizierung eines Konvertierungsoperators ist jedoch nicht Teil der Signatur des Operators. Daher kann eine Klasse oder Struktur nicht sowohl einen Verbreiterungskonvertierungsoperator als auch einen schmalen Konvertierungsoperator mit denselben Quell- und Zieltypen deklarieren.

Ein benutzerdefinierter Konvertierungsoperator muss entweder in oder aus dem enthaltenden Typ konvertiert werden , z. B. ist es möglich, dass eine Klasse C eine Konvertierung von C zu Integer und von Integer in C, aber nicht von Integer in .Boolean Wenn der enthaltende Typ ein generischer Typ ist, müssen die Typparameter mit den Typparametern des enthaltenden Typs übereinstimmen. Außerdem ist es nicht möglich, eine systeminterne (d. h. nicht benutzerdefinierte) Konvertierung neu zu definieren. Daher kann ein Typ keine Konvertierung deklarieren, wobei:

  • Der Quelltyp und der Zieltyp sind identisch.

  • Sowohl der Quelltyp als auch der Zieltyp sind nicht der Typ, der den Konvertierungsoperator definiert.

  • Der Quelltyp oder der Zieltyp ist ein Schnittstellentyp.

  • Der Quelltyp und die Zieltypen sind durch Vererbung (einschließlich Object) verknüpft.

Die einzige Ausnahme für diese Regeln gilt für Nullwertetypen. Da Nullwertetypen nicht über eine tatsächliche Typdefinition verfügen, kann ein Werttyp benutzerdefinierte Konvertierungen für die nullable Version des Typs deklarieren. Bei der Bestimmung, ob ein Typ eine bestimmte benutzerdefinierte Konvertierung deklarieren kann, werden die ? Modifizierer zunächst von allen Typen entfernt, die für die Gültigkeitsprüfung an der Deklaration beteiligt sind. Daher ist die folgende Deklaration gültig, da S eine Konvertierung von S zu T:

Structure T
    ...
End Structure

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

Die folgende Deklaration ist jedoch ungültig, da die Struktur S keine Konvertierung von S :S

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

Operatorzuordnung

Da der von Visual Basic unterstützte Operatorsatz möglicherweise nicht genau mit dem Satz von Operatoren übereinstimmt, die andere Sprachen im .NET Framework verwenden, werden einige Operatoren speziell anderen Operatoren zugeordnet, wenn sie definiert oder verwendet werden. Dies gilt insbesondere in folgenden Fällen:

  • Wenn Sie einen integralen Abteilungsoperator definieren, wird automatisch ein normaler Divisionsoperator (nur von anderen Sprachen verwendet) definiert, der den integralen Abteilungsoperator aufruft.

  • Das Überladen des Operators AndOr und die NotOperatoren überladen nur den bitweisen Operator aus der Perspektive anderer Sprachen, die zwischen logischen und bitweisen Operatoren unterscheiden.

  • Eine Klasse, die nur die logischen Operatoren in einer Sprache überladen, die zwischen logischen und bitweisen Operatoren (d. h. sprachen, die op_LogicalNot, op_LogicalAndbzw op_LogicalOr . für Not, Andbzw Or. für ) verwenden, ihre logischen Operatoren den logischen Operatoren von Visual Basic zugeordnet werden. Wenn sowohl die logischen als auch die bitweisen Operatoren überladen sind, werden nur die bitweisen Operatoren verwendet.

  • Durch die Überladung der << Operatoren und >> Operatoren werden nur die signierten Operatoren aus der Perspektive anderer Sprachen überladen, die zwischen signierten und nicht signierten Schichtoperatoren unterscheiden.

  • Eine Klasse, die nur einen nicht signierten Schichtoperator überladen, wird der nicht signierte Schichtoperator dem entsprechenden Visual Basic-Schichtoperator zugeordnet. Wenn sowohl ein nicht signierter als auch ein signierter Schichtoperator überladen ist, wird nur der signierte Schichtoperator verwendet.