Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
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:
OverridableundNotOverridableschließen sich gegenseitig aus und können nicht kombiniert werden.MustOverrideimpliziertOverridable(und kann dies nicht angeben) und kann nicht mitNotOverridable.NotOverridablekann nicht mitOverridableoderMustOverridekombiniert werden und muss mitOverrides.OverridesimpliziertOverridable(und kann dies nicht angeben) und kann nicht mitMustOverride.
Es gibt auch zusätzliche Einschränkungen für überschreibbare Methoden:
Eine
MustOverrideMethode enthält möglicherweise keinen Methodentext oder einEndKonstrukt, überschreibt möglicherweise keine andere Methode und wird nur inMustInheritKlassen angezeigt.Wenn eine Methode angibt
Overridesund keine übereinstimmende Basismethode zum Überschreiben vorhanden ist, tritt ein Kompilierungszeitfehler auf. Eine Außerkraftsetzungsmethode darf nicht angegebenShadowswerden.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 Friendanderen Assembly überschreibt, die keinen Zugriff hatFriend, angebenProtectedmuss (nichtProtected Friend).PrivateMethoden dürfen weder andereNotOverridableMethoden außerOverridableKraftMustOverridesetzen noch andere Methoden überschreiben.Methoden in
NotInheritableKlassen werden möglicherweise nicht deklariertOverridableoderMustOverride.
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
ParamArrayArgument angegebene Argument kann ein einzelner Ausdruck eines Typs sein, der auf denParamArrayTyp erweitert wird. In diesem Fall verhält sich diesParamArraygenau 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 ElementsParamArrayumgewandelt wird. In diesem Fall erstellt der Aufruf eine Instanz desParamArrayTyps 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
WithEventsoderMyClassMedasMyBaseSchlü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
XEventHandlereingefü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
PrivateInstanzvariable, die als Stellvertretung mit dem NamenXEventeingegeben wurde.Zwei Benannte
add_XMethoden, dieremove_Xnicht 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 derAddHandlerDeklaration entspricht.Eine Methode namens
remove_X, die derRemoveHandlerDeklaration entspricht.Eine Methode namens
fire_X, die derRaiseEventDeklaration 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)oderSystem.TypedEventHandle(Of T, U), und andere nicht zulassen.Das
XEventFeld hat TypSystem.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenTable(Of T), wobeiTes 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
MezugegriffenMyClasswerden.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
GetAccessor und einenSetAccessor verfügen. Die Eigenschaft wird als Lese-/Schreibzugriffseigenschaft bezeichnet.Wenn die Eigenschaft den
ReadOnlyModifizierer angibt, muss die Eigenschaft über einenGetAccessor verfügen und möglicherweise keinen Accessor habenSet. 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
WriteOnlyModifizierer angibt, muss die Eigenschaft über einenSetAccessor verfügen und möglicherweise keinen Accessor habenGet. 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:
Privateist restriktiver alsPublic,Protected Friend, ,ProtectedoderFriend.Friendist restriktiver alsProtected FriendoderPublic.Protectedist restriktiver alsProtected FriendoderPublic.Protected Friendist restriktiver alsPublic.
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. DerPublicModifizierer kann in Kontexten weggelassen werden, in denen der Modifizierer angenommen wird.Die Parameter eines Operators können nicht deklariert
ByRefwerden oderOptionalParamArray.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
ReturnAnweisung 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
NotOperator (entsprechende Methode:op_OnesComplement)Die und
IsFalsedieIsTrueOperatoren (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\, ModuloModund 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_GreaterThanOrEqualHinweis: Während der Gleichheitsoperator überladen werden kann, kann der Zuordnungsoperator (nur in Zuordnungsanweisungen verwendet) nicht überladen werden.Der
LikeOperator (entsprechende Methode:op_Like)Der Verkettungsoperator
&(entsprechende Methode:op_Concatenate)Die logischen
OrAndUndXorOperatoren (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
AndOrund dieNotOperatoren ü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_LogicalAndbzwop_LogicalOr. fürNot,AndbzwOr. 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.
Visual Basic language spec