Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Składowe typu definiują lokalizacje magazynu i kod wykonywalny. Mogą to być metody, konstruktory, zdarzenia, stałe, zmienne i właściwości.
Implementacja metody interfejsu
Metody, zdarzenia i właściwości mogą implementować elementy członkowskie interfejsu. Aby zaimplementować element członkowski interfejsu, deklaracja składowa określa Implements słowo kluczowe i wyświetla co najmniej jeden element członkowski interfejsu.
ImplementsClause
: ( 'Implements' ImplementsList )?
;
ImplementsList
: InterfaceMemberSpecifier ( Comma InterfaceMemberSpecifier )*
;
InterfaceMemberSpecifier
: NonArrayTypeName Period IdentifierOrKeyword
;
Metody i właściwości implementujące elementy członkowskie interfejsu są niejawnie NotOverridable , chyba że zadeklarowane jako MustOverride, Overridablelub przesłaniają inny element członkowski. Jest to błąd elementu członkowskiego implementowania elementu członkowskiego interfejsu jako Shared. Ułatwienia dostępu członka nie mają wpływu na jego zdolność do implementowania elementów członkowskich interfejsu.
Aby implementacja interfejsu jest prawidłowa, implementuje listę typu zawierającego musi nazwać interfejs, który zawiera zgodny element członkowski. Zgodny element członkowski jest elementem, którego podpis jest zgodny z podpisem implementowania elementu członkowskiego. Jeśli jest implementowany interfejs ogólny, argument typu podany w klauzuli Implements jest zastępowany podpisem podczas sprawdzania zgodności. Przykład:
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
Jeśli zdarzenie zadeklarowane przy użyciu typu delegata implementuje zdarzenie interfejsu, to zgodne zdarzenie jest zdarzeniem, którego bazowy typ delegata jest tego samego typu. W przeciwnym razie zdarzenie używa typu delegata ze zdarzenia interfejsu, które implementuje. Jeśli takie zdarzenie implementuje wiele zdarzeń interfejsu, wszystkie zdarzenia interfejsu muszą mieć ten sam podstawowy typ delegata. Przykład:
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
Element członkowski interfejsu na liście implementów jest określany przy użyciu nazwy typu, kropki i identyfikatora. Nazwa typu musi być interfejsem na liście implementów lub podstawowym interfejsem interfejsu na liście implementów, a identyfikator musi być członkiem określonego interfejsu. Jeden element członkowski może implementować więcej niż jeden pasujący element członkowski interfejsu.
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
Jeśli implementowany element członkowski interfejsu jest niedostępny we wszystkich jawnie zaimplementowanych interfejsach ze względu na dziedziczenie wielu interfejsów, element członkowski implementowania musi jawnie odwoływać się do interfejsu podstawowego, na którym jest dostępny element członkowski. Na przykład jeśli I1 element członkowski i I2 zawiera element członkowski M, i I3 dziedziczy z I1 i I2, implementacja I3 typu implementuje I1.M i I2.M. Jeśli w tle interfejsu są pomnożone dziedziczone elementy członkowskie, typ implementujący będzie musiał zaimplementować dziedziczone elementy członkowskie i elementy członkowskie w tle.
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
Jeśli zaimplementowano interfejs zawierający element członkowski interfejsu jest ogólny, należy podać te same argumenty typu co implementowane interfejsy. Przykład:
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
Metody
Metody zawierają instrukcje wykonywalne programu.
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
;
Metody, które mają opcjonalną listę parametrów i opcjonalną wartość zwracaną, są współużytkowane lub nieudostępniane. Metody udostępnione są dostępne za pośrednictwem klasy lub wystąpień klasy. Metody nieudzielone, nazywane również metodami wystąpień, są dostępne za pośrednictwem wystąpień klasy. W poniższym przykładzie przedstawiono klasę Stack z kilkoma metodami udostępnionymi (Clone i Flip) oraz kilkoma metodami wystąpienia (Push, Pop, i 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
Metody mogą być przeciążone, co oznacza, że wiele metod może mieć taką samą nazwę, o ile mają unikatowe podpisy. Podpis metody składa się z liczby i typów jego parametrów. Podpis metody nie zawiera w szczególności zwracanego typu lub modyfikatorów parametrów, takich jak Optional, ByRef lub ParamArray. W poniższym przykładzie przedstawiono klasę z wieloma przeciążeniami:
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
Dane wyjściowe programu to:
F()
F(Integer)
F(Object)
F(Integer, Integer)
F(Integer())
G(String)
G(String, Optional String)
Przeciążenia, które różnią się tylko parametrami opcjonalnymi, mogą służyć do "przechowywania wersji" bibliotek. Na przykład wersja 1 biblioteki może zawierać funkcję z opcjonalnymi parametrami:
Sub fopen(fileName As String, Optional accessMode as Integer = 0)
Następnie wersja 2 biblioteki chce dodać kolejny opcjonalny parametr "password" i chce to zrobić bez zgodności ze źródłem powodującym niezgodność (tak aby aplikacje używane do docelowego elementu docelowego w wersji 1 mogły zostać ponownie skompilowane) i bez przerywania zgodności binarnej (tak aby aplikacje używane do odwoływania się do wersji 1 mogły teraz odwoływać się do wersji 2 bez ponownej kompilacji). W ten sposób będzie wyglądać wersja 2:
Sub fopen(file As String, mode as Integer)
Sub fopen(file As String, Optional mode as Integer = 0, Optional pword As String = "")
Należy pamiętać, że parametry opcjonalne w publicznym interfejsie API nie są zgodne ze specyfikacją CLS. Można je jednak używać co najmniej w języku Visual Basic i C#4 i F#.
Deklaracje metod regularnych, asynchronicznych i iteratorowych
Istnieją dwa typy metod: podroutines, które nie zwracają wartości, i funkcje, które wykonują. Treść i End konstrukcja metody mogą zostać pominięte tylko wtedy, gdy metoda jest zdefiniowana w interfejsie lub ma MustOverride modyfikator. Jeśli w funkcji nie określono żadnego typu zwrotnego, a używane są ścisłe semantyka, wystąpi błąd czasu kompilacji; w przeciwnym razie typ jest niejawnie Object lub typ znaku typu metody. Domena ułatwień dostępu typu zwracanego i typów parametrów metody musi być taka sama jak lub nadzbiór domeny ułatwień dostępu samej metody.
Regularna metoda jest metodą bez Async modyfikatorów ani Iterator modyfikatorów. Może to być podroutyna lub funkcja. Sekcja Metody regularne zawiera szczegółowe informacje o tym, co się stanie po wywołaniu zwykłej metody.
Metoda iteratora jest metodą z modyfikatorem Iterator i bez Async modyfikatora. Musi być funkcją, a jej zwracany typ musi mieć IEnumeratorwartość , IEnumerablelub IEnumerator(Of T)IEnumerable(Of T) dla niektórych Tparametrów i nie ByRef musi mieć żadnych parametrów.
Metody iteratora sekcji zawierają szczegółowe informacje o tym, co dzieje się po wywołaniu metody iteratora.
Metoda asynchronizatora jest metodą z Async modyfikatorem i bez Iterator modyfikatora. Musi to być podroutyna lub funkcja z typem Task zwracanym lub Task(Of T) dla niektórych Tparametrów i nie ByRef musi mieć żadnych parametrów. Sekcja Metody asynchroniczne zawiera szczegółowe informacje o tym, co się stanie po wywołaniu metody asynchronicznej.
Jest to błąd czasu kompilacji, jeśli metoda nie jest jednym z tych trzech rodzajów metody.
Instrukcje podrzędne i deklaracje funkcji są specjalne, ponieważ ich instrukcje początkowe i końcowe muszą zaczynać się na początku wiersza logicznego. Ponadto treść deklaracji innejMustOverride niż podroutine lub funkcja musi zaczynać się na początku wiersza logicznego. Przykład:
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
Deklaracje metody zewnętrznej
Deklaracja metody zewnętrznej wprowadza nową metodę, której implementacja jest dostarczana poza programem.
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
;
Ponieważ deklaracja metody zewnętrznej nie zawiera rzeczywistej implementacji, nie ma treści ani End konstrukcji metody. Metody zewnętrzne są niejawnie udostępniane, mogą nie mieć parametrów typu i mogą nie obsługiwać zdarzeń ani implementować elementów członkowskich interfejsu. Jeśli w funkcji nie określono żadnego zwracanego typu, a używane są ścisłe semantyka, wystąpi błąd czasu kompilacji. W przeciwnym razie typ jest niejawnie Object lub typ znaku typu metody. Domena ułatwień dostępu typu zwracanego i typów parametrów metody zewnętrznej musi być taka sama jak lub nadzbiór domeny ułatwień dostępu samej metody zewnętrznej.
Klauzula biblioteki deklaracji metody zewnętrznej określa nazwę pliku zewnętrznego, który implementuje metodę. Opcjonalna klauzula aliasu to ciąg, który określa liczbę porządkową (poprzedzoną znakiem # ) lub nazwę metody w pliku zewnętrznym. Można również określić modyfikator zestawu znaków, który zarządza zestawem znaków używanym do marshalingu ciągów podczas wywołania metody zewnętrznej.
Unicode Modyfikator marshaluje wszystkie ciągi do wartości Unicode, Ansi modyfikator marshaluje wszystkie ciągi do wartości ANSI, a Auto modyfikator marshaluje ciągi zgodnie z regułami programu .NET Framework na podstawie nazwy metody lub nazwy aliasu, jeśli określono. Jeśli nie określono modyfikatora, wartość domyślna to Ansi.
Jeśli Ansi określono lub Unicode jest określona, nazwa metody jest sprawdzana w pliku zewnętrznym bez żadnych modyfikacji. Jeśli Auto zostanie określony, wyszukiwanie nazwy metody zależy od platformy. Jeśli platforma jest uważana za ANSI (na przykład Windows 95, Windows 98, Windows ME), nazwa metody jest sprawdzana bez modyfikacji. Jeśli wyszukiwanie zakończy się niepowodzeniem A , element zostanie dołączony, a wyszukiwanie ponowiło próbę. Jeśli platforma jest uważana za Unicode (na przykład Windows NT, Windows 2000, Windows XP), W jest dołączana, a nazwa jest wyszukana. Jeśli wyszukiwanie zakończy się niepowodzeniem, wyszukiwanie zostanie ponownie wypróbowane bez elementu W. Przykład:
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
Typy danych przekazywane do metod zewnętrznych są marshalowane zgodnie z konwencjami marshalingu danych programu .NET Framework z jednym wyjątkiem. Zmienne ciągu przekazywane przez wartość (czyli ByVal x As String) są marshalowane do typu BSTR automatyzacji OLE, a zmiany wprowadzone w metodzie zewnętrznej są odzwierciedlane z powrotem w argumencie ciągu. Jest to spowodowane tym, że typ String metod zewnętrznych jest modyfikowalny, a ten specjalny marshall naśladuje to zachowanie. Parametry ciągu przekazywane przez odwołanie (tj. ByRef x As String) są marshalowane jako wskaźnik do typu BSTR automatyzacji OLE. Można zastąpić te specjalne zachowania, określając System.Runtime.InteropServices.MarshalAsAttribute atrybut w parametrze .
W przykładzie pokazano użycie metod zewnętrznych:
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
Metody, które można zastąpić
Modyfikator Overridable wskazuje, że metoda jest zastępowalna. Modyfikator Overrides wskazuje, że metoda zastępuje metodę zastępowalną typu podstawowego, która ma ten sam podpis. Modyfikator NotOverridable wskazuje, że nie można jeszcze bardziej zastąpić metody zastępowalnej. Modyfikator MustOverride wskazuje, że metoda musi zostać zastąpiona w klasach pochodnych.
Niektóre kombinacje tych modyfikatorów są nieprawidłowe:
OverridableiNotOverridablewzajemnie się wykluczają i nie mogą być połączone.MustOverrideoznaczaOverridable(i tak nie można go określić) i nie można go połączyć zNotOverridable.NotOverridablenie można połączyć zOverridablelubMustOverridei musi być połączony zOverrides.OverridesoznaczaOverridable(i tak nie można go określić) i nie można go połączyć zMustOverride.
Istnieją również dodatkowe ograniczenia dotyczące metod zastępowalnych:
MustOverrideMetoda może nie zawierać treści metody lubEndkonstrukcji, może nie zastąpić innej metody i może być wyświetlana tylko wMustInheritklasach.Jeśli metoda określa
Overridesi nie ma pasującej metody podstawowej do zastąpienia, wystąpi błąd czasu kompilacji. Metoda zastępowania może nie określać wartościShadows.Metoda może nie zastąpić innej metody, jeśli domena ułatwień dostępu metody zastępowania nie jest równa domenie ułatwień dostępu zastępowanej metody. Jednym wyjątkiem jest to, że metoda przesłaniania
Protected Friendmetody w innym zestawie, który nie maFrienddostępu, musi określićProtected(nieProtected Friend).Privatemetody nie mogą byćOverridablemetodami ,NotOverridablelubMustOverride, ani nie mogą zastąpić innych metod.Metody w
NotInheritableklasach mogą nie być zadeklarowaneOverridablelubMustOverride.
W poniższym przykładzie przedstawiono różnice między metodami, które można zastąpić i nie można zastąpić:
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
W tym przykładzie klasa Base wprowadza metodę F i metodę OverridableG. Klasa Derived wprowadza nową metodę F, w związku z czym w tle dziedziczone Fmetody , a także zastępuje dziedziczonej metody G. W przykładzie są generowane następujące dane wyjściowe:
Base.F
Derived.F
Derived.G
Derived.G
Zwróć uwagę, że instrukcja b.G() wywołuje metodę Derived.G, a nie Base.G. Jest to spowodowane tym, że typ czasu wykonywania wystąpienia (czyli Derived) zamiast typu czasu kompilacji wystąpienia (czyli Base) określa rzeczywistą implementację metody do wywołania.
Metody udostępnione
Modyfikator Shared wskazuje, że metoda jest metodą udostępnioną. Metoda udostępniona nie działa na określonym wystąpieniu typu i może być wywoływana bezpośrednio z typu, a nie za pośrednictwem określonego wystąpienia typu. Prawidłowe jest jednak użycie wystąpienia w celu zakwalifikowania metody udostępnionej. Odwołanie do Memetody , MyClasslub MyBase w metodzie udostępnionej jest nieprawidłowe. Metody udostępnione mogą nie być Overridablemetodami , NotOverridablelub MustOverride, i mogą nie zastąpić metod. Metody zdefiniowane w standardowych modułach i interfejsach mogą nie określać Sharedwartości , ponieważ są one już niejawnie Shared .
Metoda zadeklarowana w strukturze lub klasie bez Shared modyfikatora jest metodą wystąpienia. Metoda wystąpienia działa na danym wystąpieniu typu. Metody wystąpień mogą być wywoływane tylko za pośrednictwem wystąpienia typu i mogą odwoływać się do wystąpienia za pomocą Me wyrażenia.
W poniższym przykładzie przedstawiono reguły uzyskiwania dostępu do udostępnionych elementów członkowskich i wystąpień:
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
Metoda F pokazuje, że w elemencie członkowskim funkcji wystąpienia identyfikator może służyć do uzyskiwania dostępu do elementów członkowskich wystąpienia i udostępnionych członków. Metoda G pokazuje, że w elemencie członkowskim funkcji udostępnionej jest to błąd podczas uzyskiwania dostępu do elementu członkowskiego wystąpienia za pomocą identyfikatora. Metoda Main pokazuje, że w wyrażeniu dostępu członka członkowie muszą być dostęp do elementów członkowskich za pośrednictwem wystąpień, ale współużytkowane elementy członkowskie mogą być dostępne za pośrednictwem typów lub wystąpień.
Parametry metody
Parametr to zmienna, która może służyć do przekazywania informacji do metody i z niej. Parametry metody są deklarowane przez listę parametrów metody, która składa się z co najmniej jednego parametru rozdzielanego przecinkami.
ParameterList
: Parameter ( Comma Parameter )*
;
Parameter
: Attributes? ParameterModifier* ParameterIdentifier ( 'As' TypeName )?
( Equals ConstantExpression )?
;
ParameterModifier
: 'ByVal' | 'ByRef' | 'Optional' | 'ParamArray'
;
ParameterIdentifier
: Identifier IdentifierModifiers
;
Jeśli dla parametru nie określono żadnego typu, a używana jest ścisła semantyka, wystąpi błąd czasu kompilacji. W przeciwnym razie typ domyślny to Object lub typ znaku typu parametru. Nawet w ramach semantyki permissive, jeśli jeden parametr zawiera klauzulę As , wszystkie parametry muszą określać typy.
Parametry są określane jako wartości, odwołania, opcjonalne lub parametry paramarray odpowiednio przez modyfikatory ByVal, , ByRefOptionali ParamArray. Parametr, który nie określa ByRef wartości domyślnej ByVallub ByVal .
Nazwy parametrów są ograniczone do całej treści metody i są zawsze dostępne publicznie. Wywołanie metody tworzy kopię specyficzną dla tego wywołania parametrów, a lista argumentów wywołania przypisuje wartości lub odwołania do zmiennych do nowo utworzonych parametrów. Ponieważ deklaracje metod zewnętrznych i deklaracje delegatów nie mają treści, zduplikowane nazwy parametrów są dozwolone na listach parametrów, ale zniechęcone.
Identyfikator może być następnie modyfikator ? nazwy dopuszczający wartość null, aby wskazać, że jest on dopuszczany do wartości null, a także przez modyfikatory nazw tablicy, aby wskazać, że jest tablicą. Mogą być łączone, np. "ByVal x?() As Integer". Nie można używać jawnych granic tablicy; Ponadto, jeśli modyfikator nazw dopuszczający wartość null jest obecny, klauzula As musi być obecna.
Parametry wartości
Parametr wartości jest zadeklarowany za pomocą modyfikatora jawnegoByVal.
ByVal Jeśli modyfikator jest używany, ByRef modyfikator może nie zostać określony. Parametr wartości występuje z wywołaniem elementu członkowskiego, do którego należy parametr i jest inicjowany przy użyciu wartości argumentu podanego w wywołaniu. Parametr wartości przestaje istnieć po powrocie elementu członkowskiego.
Metoda może przypisywać nowe wartości do parametru wartości. Takie przypisania mają wpływ tylko na lokalną lokalizację magazynu reprezentowaną przez parametr wartości; nie mają wpływu na rzeczywisty argument podany w wywołaniu metody.
Parametr wartości jest używany, gdy wartość argumentu jest przekazywana do metody, a modyfikacje parametru nie mają wpływu na oryginalny argument. Parametr wartości odnosi się do własnej zmiennej, która różni się od zmiennej odpowiadającego argumentu. Ta zmienna jest inicjowana przez skopiowanie wartości odpowiedniego argumentu. W poniższym przykładzie przedstawiono metodę F , która ma parametr wartości o nazwie p:
Module Test
Sub F(p As Integer)
Console.WriteLine("p = " & p)
p += 1
End Sub
Sub Main()
Dim a As Integer = 1
Console.WriteLine("pre: a = " & a)
F(a)
Console.WriteLine("post: a = " & a)
End Sub
End Module
W przykładzie są generowane następujące dane wyjściowe, mimo że parametr p wartości został zmodyfikowany:
pre: a = 1
p = 1
post: a = 1
Parametry referencyjne
Parametr referencyjny jest parametrem zadeklarowanym za pomocą ByRef modyfikatora.
ByRef Jeśli modyfikator zostanie określony, ByVal modyfikator może nie być używany. Parametr referencyjny nie tworzy nowej lokalizacji magazynu. Zamiast tego parametr odwołania reprezentuje zmienną podaną jako argument w wywołaniu metody lub konstruktora. Koncepcyjnie wartość parametru referencyjnego jest zawsze taka sama jak zmienna bazowa.
Parametry odwołania działają w dwóch trybach, jako aliasy lub za pomocą kopii w kopii zapasowej.
Aliasy. Parametr odwołania jest używany, gdy parametr działa jako alias dla argumentu dostarczonego przez obiekt wywołujący. Parametr odwołania nie definiuje zmiennej, ale odnosi się do zmiennej odpowiadającego mu argumentu. Modyfikacje parametru odwołania bezpośrednio i natychmiast wpływają na odpowiedni argument. W poniższym przykładzie przedstawiono metodę Swap , która ma dwa parametry odwołania:
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
Dane wyjściowe programu to:
pre: x = 1, y = 2
post: x = 2, y = 1
Dla wywołania metody Swap w klasie Main, a reprezentuje x, i b reprezentuje y. W związku z tym wywołanie ma wpływ na zamianę wartości x i y.
W metodzie, która przyjmuje parametry odwołania, istnieje możliwość, aby wiele nazw reprezentowało tę samą lokalizację magazynu:
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
W przykładzie wywołanie metody F w metodzie przekazuje odwołanie do s dla metody i ab.G W związku z tym dla tego wywołania nazwy s, ai b wszystkie odwołują się do tej samej lokalizacji magazynu, a trzy przypisania modyfikują zmienną swystąpienia .
Kopiowanie kopii zapasowej. Jeśli typ zmiennej przekazywanej do parametru referencyjnego nie jest zgodny z typem parametru odwołania lub jeśli zmienna inna (np. właściwość) jest przekazywana jako argument do parametru referencyjnego lub jeśli wywołanie jest opóźnione, zmienna tymczasowa jest przydzielana i przekazywana do parametru odwołania. Przekazana wartość zostanie skopiowana do tej zmiennej tymczasowej przed wywołaniem metody i zostanie skopiowana z powrotem do oryginalnej zmiennej (jeśli istnieje, a jeśli jest zapisywalna), gdy metoda zwraca. W związku z tym parametr odwołania może niekoniecznie zawierać odwołanie do dokładnego przechowywania przekazywanej zmiennej, a wszelkie zmiany parametru odwołania mogą nie zostać odzwierciedlone w zmiennej do momentu zakończenia metody. Przykład:
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
W przypadku pierwszego wywołania Fzmiennej tymczasowej jest tworzona, a wartość właściwości G jest przypisywana i przekazywana do Fmetody . Po powrocie z Fzmiennej tymczasowej wartość jest przypisywana z powrotem do właściwości G. W drugim przypadku jest tworzona inna zmienna tymczasowa, a wartość d jest do niej przypisywana i przekazywana do Fmetody . W przypadku zwracania wartości Fw zmiennej tymczasowej jest zwracana do typu zmiennej Derived, i przypisana do dzmiennej . Ponieważ nie można rzutować przekazanej wartości z powrotem do Derivedelementu , w czasie wykonywania jest zgłaszany wyjątek.
Parametry opcjonalne
Opcjonalny parametr jest zadeklarowany za pomocą Optional modyfikatora. Parametry zgodne z opcjonalnym parametrem na liście parametrów formalnych muszą być również opcjonalne; błąd podczas określania Optional modyfikatora dla następujących parametrów spowoduje wyzwolenie błędu czasu kompilacji. Opcjonalny parametr typu dopuszczalnego T? do wartości null lub typu T niepustego musi określać wyrażenie e stałe, które ma być używane jako wartość domyślna, jeśli nie określono żadnego argumentu. Jeśli e zostanie obliczona Nothing wartość typu Object, wartość domyślna typu parametru zostanie użyta jako domyślna dla parametru .
CType(e, T) W przeciwnym razie musi być wyrażeniem stałym i jest ono traktowane jako domyślne dla parametru .
Parametry opcjonalne to jedyna sytuacja, w której inicjator parametru jest prawidłowy. Inicjowanie jest zawsze wykonywane jako część wyrażenia wywołania, a nie w samej treści metody.
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
Dane wyjściowe programu to:
x = 10, y = 20
x = 30, y = 40
Opcjonalne parametry nie mogą być określone w deklaracjach delegatów lub zdarzeń ani w wyrażeniach lambda.
Parametry paramArray
ParamArray parametry są deklarowane za pomocą ParamArray modyfikatora.
ParamArray Jeśli modyfikator jest obecny, ByVal modyfikator musi być określony, a żaden inny parametr nie może używać ParamArray modyfikatora.
ParamArray Typ parametru musi być tablicą jednowymiarową i musi być ostatnim parametrem na liście parametrów.
Parametr ParamArray reprezentuje nieokreśloną liczbę parametrów typu ParamArray. W samej ParamArray metodzie parametr jest traktowany jako zadeklarowany typ i nie ma specjalnych semantyki. Parametr ParamArray jest niejawnie opcjonalny z wartością domyślną pustej jednowymiarowej tablicy typu ParamArray.
Argumenty ParamArray zezwalają na określenie argumentów na jeden z dwóch sposobów wywołania metody:
Argument podany dla elementu
ParamArraymoże być pojedynczym wyrażeniem typu, które rozszerza typParamArray. W tym przypadku parametrParamArraydziała dokładnie tak jak parametr wartości.Alternatywnie wywołanie może określać zero lub więcej argumentów dla
ParamArrayelementu , gdzie każdy argument jest wyrażeniem typu niejawnie konwertowanego na typParamArrayelementu . W tym przypadku wywołanie tworzy wystąpienieParamArraytypu o długości odpowiadającej liczbie argumentów, inicjuje elementy wystąpienia tablicy z podanymi wartościami argumentów i używa nowo utworzonego wystąpienia tablicy jako rzeczywistego argumentu.
Z wyjątkiem zezwalania na zmienną liczbę argumentów w wywołaniu, parametr ParamArray jest dokładnie równoważny parametrowi wartości tego samego typu, co pokazano w poniższym przykładzie.
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
Przykład generuje dane wyjściowe
Array contains 3 elements: 1 2 3
Array contains 4 elements: 10 20 30 40
Array contains 0 elements:
Pierwsze wywołanie F po prostu przekazuje tablicę a jako parametr wartości. Drugie wywołanie F automatycznie tworzy tablicę czteroelementową z podanymi wartościami elementu i przekazuje to wystąpienie tablicy jako parametr wartości. Podobnie trzecie wywołanie F tworzy tablicę zero-element i przekazuje to wystąpienie jako parametr wartości. Drugie i trzecie wywołania są dokładnie równoważne pisaniu:
F(New Integer() {10, 20, 30, 40})
F(New Integer() {})
ParamArray parametrów nie można określić w deklaracjach delegatów lub zdarzeń.
Obsługa zdarzeń
Metody mogą deklaratywnie obsługiwać zdarzenia zgłaszane przez obiekty w wystąpieniu lub współużytkowanych zmiennych. Aby obsłużyć zdarzenia, deklaracja metody określa Handles słowo kluczowe i wyświetla co najmniej jedno zdarzenie.
HandlesClause
: ( 'Handles' EventHandlesList )?
;
EventHandlesList
: EventMemberSpecifier ( Comma EventMemberSpecifier )*
;
EventMemberSpecifier
: Identifier Period IdentifierOrKeyword
| 'MyBase' Period IdentifierOrKeyword
| 'MyClass' Period IdentifierOrKeyword
| 'Me' Period IdentifierOrKeyword
;
Zdarzenie na Handles liście jest określane przez dwa identyfikatory oddzielone kropką:
Pierwszy identyfikator musi być wystąpieniem lub zmienną udostępnioną w typie zawierającym, który określa
WithEventsmodyfikator lubMyBasesłowo kluczowe lub lubMyClassMe; w przeciwnym razie występuje błąd czasu kompilacji. Ta zmienna zawiera obiekt, który będzie zgłaszać zdarzenia obsługiwane przez tę metodę.Drugi identyfikator musi określać element członkowski typu pierwszego identyfikatora. Element członkowski musi być zdarzeniem i może być udostępniony. Jeśli dla pierwszego identyfikatora określono zmienną udostępnioną, zdarzenie musi być współużytkowane lub wyniki błędu.
Metoda M obsługi jest uznawana za prawidłową procedurę obsługi zdarzeń dla zdarzenia E , jeśli instrukcja AddHandler E, AddressOf M będzie również prawidłowa.
AddHandler W przeciwieństwie do instrukcji, jednak jawne programy obsługi zdarzeń umożliwiają obsługę zdarzenia bez argumentów bez względu na to, czy są używane ścisłe semantyka, czy nie:
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
Pojedynczy element członkowski może obsługiwać wiele pasujących zdarzeń, a wiele metod może obsługiwać pojedyncze zdarzenie. Dostępność metody nie ma wpływu na jej zdolność do obsługi zdarzeń. W poniższym przykładzie pokazano, jak metoda może obsługiwać zdarzenia:
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
Spowoduje to wyświetlenie następującego komunikatu:
Raised
Raised
Typ dziedziczy wszystkie programy obsługi zdarzeń udostępniane przez jego typ podstawowy. Typ pochodny nie może w żaden sposób zmienić mapowań zdarzeń, które dziedziczy z jego typów podstawowych, ale może dodać dodatkowe procedury obsługi do zdarzenia.
Metody rozszerzania
Metody można dodawać do typów spoza deklaracji typu przy użyciu metod rozszerzeń. Metody rozszerzenia to metody z atrybutem System.Runtime.CompilerServices.ExtensionAttribute zastosowanym do nich. Można je zadeklarować tylko w modułach standardowych i musi mieć co najmniej jeden parametr, który określa typ, który rozszerza metodę. Na przykład następująca metoda rozszerzenia rozszerza typ String:
Imports System.Runtime.CompilerServices
Module StringExtensions
<Extension> _
Sub Print(s As String)
Console.WriteLine(s)
End Sub
End Module
Uwaga. Mimo że język Visual Basic wymaga zadeklarowania metod rozszerzeń w module standardowym, inne języki, takie jak C#, mogą zezwalać na deklarowanie ich w innych typach. Jeśli metody są zgodne z innymi konwencjami opisanymi tutaj, a typ zawierający nie jest otwartym typem ogólnym i nie można utworzyć wystąpienia wystąpienia, język Visual Basic rozpozna metody rozszerzenia.
Po wywołaniu metody rozszerzenia wystąpienie, na które jest wywoływane, jest przekazywane do pierwszego parametru. Nie można zadeklarować Optional pierwszego parametru ani ParamArray. Dowolny typ, w tym parametr typu, może być wyświetlany jako pierwszy parametr metody rozszerzenia. Na przykład następujące metody rozszerzają typy Integer(), dowolny typ, który implementuje System.Collections.Generic.IEnumerable(Of T), i dowolny typ w ogóle:
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
Jak pokazano w poprzednim przykładzie, interfejsy można rozszerzyć. Metody rozszerzenia interfejsu zapewniają implementację metody, więc typy, które implementują interfejs, który ma zdefiniowane metody rozszerzenia, nadal implementują tylko elementy członkowskie pierwotnie zadeklarowane przez interfejs. Przykład:
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
Metody rozszerzenia mogą również mieć ograniczenia typu dla parametrów typu i, podobnie jak w przypadku metod ogólnych innych niż rozszerzenia, argument typu można wywnioskować:
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
Dostęp do metod rozszerzeń można również uzyskać za pośrednictwem wyrażeń niejawnych wystąpień w rozszerzonym typie:
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
Dla celów ułatwień dostępu metody rozszerzenia są również traktowane jako elementy członkowskie modułu standardowego, w ramach którego są deklarowane — nie mają dodatkowego dostępu do składowych typu, który wykraczają poza dostęp, jaki mają ze względu na kontekst deklaracji.
Metody rozszerzeń są dostępne tylko wtedy, gdy metoda modułu standardowego znajduje się w zakresie. W przeciwnym razie oryginalny typ nie zostanie rozszerzony. Przykład:
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
Odwołanie do typu, gdy tylko metoda rozszerzenia w typie jest dostępna, nadal spowoduje wygenerowanie błędu czasu kompilacji.
Należy pamiętać, że metody rozszerzenia są uważane za elementy członkowskie typu we wszystkich kontekstach, w których są powiązane elementy członkowskie, takie jak silnie typizowane For Each wzorce. Przykład:
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
Można również utworzyć delegatów odwołujących się do metod rozszerzeń. W związku z tym kod:
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
jest w przybliżeniu równoważne:
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
Uwaga. Visual Basic zwykle wstawia sprawdzanie wywołania metody wystąpienia, które powoduje System.NullReferenceException wystąpienie wystąpienia, jeśli wystąpienie wywoływane metody to Nothing. W przypadku metod rozszerzeń nie ma wydajnego sposobu wstawienia tego sprawdzania, więc metody rozszerzeń będą musiały jawnie sprawdzić, czy Nothing.
Uwaga. Typ wartości zostanie w polu przekazywanym jako ByVal argument do parametru typizowanego jako interfejs. Oznacza to, że skutki uboczne metody rozszerzenia będą działać na kopii struktury zamiast oryginalnej. Chociaż język nie ogranicza pierwszego argumentu metody rozszerzenia, zaleca się, aby metody rozszerzeń nie były używane do rozszerzania typów wartości lub że podczas rozszerzania typów wartości, pierwszy parametr jest przekazywany ByRef w celu zapewnienia, że efekty uboczne działają na oryginalnej wartości.
Metody częściowe
Metoda częściowa to metoda określająca podpis, ale nie treść metody. Treść metody może zostać dostarczona przez inną deklarację metody o tej samej nazwie i podpisie, najprawdopodobniej w innej częściowej deklaracji typu. Przykład:
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
W tym przykładzie częściowa deklaracja klasy MyForm deklaruje metodę ValidateControls częściową bez implementacji. Konstruktor w deklaracji częściowej wywołuje metodę częściową, mimo że w pliku nie ma żadnej treści. Druga częściowa deklaracja MyForm następnie dostarcza implementację metody .
Metody częściowe można wywołać niezależnie od tego, czy podano treść; jeśli nie podano treści metody, wywołanie jest ignorowane. Przykład:
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
Wszystkie wyrażenia przekazywane jako argumenty do wywołania metody częściowej, które są ignorowane, również są ignorowane i nie są oceniane. (Uwaga. Oznacza to, że metody częściowe są bardzo wydajnym sposobem zapewnienia zachowania zdefiniowanego w dwóch typach częściowych, ponieważ metody częściowe nie mają żadnych kosztów, jeśli nie są używane.
Deklaracja metody częściowej musi być zadeklarowana jako Private i musi być zawsze podroutyną bez instrukcji w jej treści. Metody częściowe nie mogą implementować metod interfejsu, chociaż metoda dostarczająca ich treść może.
Tylko jedna metoda może dostarczyć treść do metody częściowej. Metoda dostarczająca treść do metody częściowej musi mieć ten sam podpis co metoda częściowa, te same ograniczenia dotyczące dowolnych parametrów typu, tych samych modyfikatorów deklaracji i tych samych parametrów i nazw parametrów typu. Atrybuty metody częściowej i metody dostarczającej jej treść są scalane, podobnie jak wszystkie atrybuty parametrów metod. Podobnie lista zdarzeń, które obsługują metody, jest scalona. Przykład:
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
Konstruktory
Konstruktory to specjalne metody, które umożliwiają kontrolę nad inicjowaniem. Są one uruchamiane po rozpoczęciu programu lub utworzeniu wystąpienia typu. W przeciwieństwie do innych elementów członkowskich konstruktory nie są dziedziczone i nie wprowadzają nazwy do przestrzeni deklaracji typu. Konstruktory mogą być wywoływane tylko przez wyrażenia tworzenia obiektów lub programu .NET Framework; nigdy nie mogą być wywoływane bezpośrednio.
Uwaga. Konstruktory mają to samo ograniczenie dotyczące umieszczania wierszy, które mają podrouty. Instrukcja początkowa, instrukcja end i blok muszą być wyświetlane na początku wiersza logicznego.
ConstructorMemberDeclaration
: Attributes? ConstructorModifier* 'Sub' 'New'
( OpenParenthesis ParameterList? CloseParenthesis )? LineTerminator
Block?
'End' 'Sub' StatementTerminator
;
ConstructorModifier
: AccessModifier
| 'Shared'
;
Konstruktory wystąpień
Konstruktory wystąpień inicjują wystąpienia typu i są uruchamiane przez program .NET Framework podczas tworzenia wystąpienia. Lista parametrów konstruktora podlega tym samym regułom co lista parametrów metody. Konstruktory wystąpień mogą być przeciążone.
Wszystkie konstruktory w typach referencyjnych muszą wywołać inny konstruktor. Jeśli wywołanie jest jawne, musi być pierwszą instrukcją w treści metody konstruktora. Instrukcja może wywołać inny konstruktor wystąpienia typu — na przykład lub MyClass.New(...) — albo, jeśli nie jest strukturą, Me.New(...) może wywołać konstruktor wystąpienia typu podstawowego — na przykład MyBase.New(...). Wywoływanie samego konstruktora jest nieprawidłowe. Jeśli konstruktor pomija wywołanie innego konstruktora, MyBase.New() jest niejawne. Jeśli nie ma konstruktora typu podstawowego bez parametrów, wystąpi błąd czasu kompilacji. Ponieważ Me nie jest uważany za konstruowany do momentu wywołania konstruktora klasy bazowej, parametry instrukcji wywołania konstruktora nie mogą odwoływać się Medo , MyClasslub MyBase niejawnie lub jawnie.
Gdy pierwsza instrukcja konstruktora ma postać MyBase.New(...), konstruktor niejawnie wykonuje inicjacje określone przez inicjatory zmiennych wystąpienia zadeklarowanych w typie. Odpowiada to sekwencji przypisań, które są wykonywane natychmiast po wywołaniu bezpośredniego konstruktora typu podstawowego. Takie porządkowanie gwarantuje, że wszystkie zmienne wystąpienia podstawowego są inicjowane przez inicjatory zmiennych przed wykonaniem wszelkich instrukcji, które mają dostęp do wystąpienia. Przykład:
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
Gdy New B() jest używane do utworzenia Bwystąpienia programu , generowane są następujące dane wyjściowe:
x = 1, y = 1
Wartość parametru y to 1 , ponieważ inicjator zmiennej jest wykonywany po wywołaniu konstruktora klasy bazowej. Inicjatory zmiennych są wykonywane w kolejności tekstowej, która jest wyświetlana w deklaracji typu.
Gdy typ deklaruje tylko Private konstruktory, nie jest ogólnie możliwe, aby inne typy pochodziły z typu lub tworzenia wystąpień typu; jedynym wyjątkiem są typy zagnieżdżone w typie.
Private konstruktory są często używane w typach, które zawierają tylko Shared elementy członkowskie.
Jeśli typ nie zawiera deklaracji konstruktora wystąpienia, zostanie automatycznie podany domyślny konstruktor. Domyślny konstruktor po prostu wywołuje konstruktor bez parametrów typu podstawowego. Jeśli bezpośredni typ podstawowy nie ma dostępnego konstruktora bez parametrów, wystąpi błąd czasu kompilacji. Zadeklarowany typ dostępu dla konstruktora domyślnego ma Public wartość , chyba że typ to MustInherit, w którym przypadku domyślnym konstruktorem jest Protected.
Uwaga. Domyślny dostęp do domyślnego MustInherit konstruktora typu jest Protected spowodowany tym, że MustInherit klasy nie mogą być tworzone bezpośrednio. Nie ma więc sensu w tworzeniu domyślnego konstruktora Public.
W poniższym przykładzie podano konstruktor domyślny, ponieważ klasa nie zawiera deklaracji konstruktora:
Class Message
Dim sender As Object
Dim text As String
End Class
W związku z tym przykład jest dokładnie równoważny z następującymi elementami:
Class Message
Dim sender As Object
Dim text As String
Sub New()
End Sub
End Class
Konstruktory domyślne, które są emitowane do projektanta wygenerowanej klasy oznaczonej atrybutem Microsoft.VisualBasic.CompilerServices.DesignerGeneratedAttribute , wywoła metodę Sub InitializeComponent(), jeśli istnieje, po wywołaniu konstruktora podstawowego. (Uwaga. Dzięki temu projektant wygenerowane pliki, takie jak te utworzone przez projektanta WinForms, pomijają konstruktora w pliku projektanta. Dzięki temu programista może określić go samodzielnie, jeśli tak wybierze.
Konstruktory udostępnione
Współużytkowane konstruktory inicjują współużytkowane zmienne typu; są one uruchamiane po rozpoczęciu wykonywania programu, ale przed wszelkimi odwołaniami do elementu członkowskiego typu. Współużytkowany konstruktor określa Shared modyfikator, chyba że znajduje się w module standardowym, w którym przypadku Shared modyfikator jest implikowany.
W przeciwieństwie do konstruktorów wystąpień, konstruktory udostępnione mają niejawny dostęp publiczny, nie mają parametrów i mogą nie wywoływać innych konstruktorów. Przed pierwszą instrukcją w konstruktorze udostępnionym współużytkowany konstruktor niejawnie wykonuje inicjacje określone przez inicjatory zmiennych udostępnionych zadeklarowanych w typie. Koreluje to z sekwencją przypisań, które są wykonywane natychmiast po wejściu do konstruktora. Inicjatory zmiennych są wykonywane w kolejności tekstowej, która jest wyświetlana w deklaracji typu.
W poniższym przykładzie pokazano klasę Employee z współużytkowanym konstruktorem, który inicjuje zmienną udostępnioną:
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
Istnieje oddzielny współużytkowany konstruktor dla każdego zamkniętego typu ogólnego. Ponieważ współużytkowany konstruktor jest wykonywany dokładnie raz dla każdego zamkniętego typu, jest to wygodne miejsce do wymuszania kontroli czasu wykonywania dla parametru typu, którego nie można sprawdzić w czasie kompilacji za pośrednictwem ograniczeń. Na przykład następujący typ używa współużytkowanego konstruktora, aby wymusić, że parametr typu to Integer lub 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
Dokładnie wtedy, gdy współużytkowane konstruktory są uruchamiane, jest głównie zależne od implementacji, choć kilka gwarancji jest zapewnianych, jeśli konstruktor współużytkowany jest jawnie zdefiniowany:
Konstruktory udostępnione są uruchamiane przed pierwszym dostępem do dowolnego pola statycznego typu.
Konstruktory udostępnione są uruchamiane przed pierwszym wywołaniem dowolnej statycznej metody typu.
Konstruktory udostępnione są uruchamiane przed pierwszym wywołaniem dowolnego konstruktora dla typu.
Powyższe gwarancje nie mają zastosowania w sytuacji, gdy współużytkowany konstruktor jest niejawnie tworzony dla udostępnionych inicjatorów. Dane wyjściowe z poniższego przykładu są niepewne, ponieważ dokładna kolejność ładowania i dlatego współużytkowanego wykonywania konstruktora nie jest zdefiniowana:
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
Dane wyjściowe mogą być następujące:
Init A
A.F
Init B
B.F
lub
Init B
Init A
A.F
B.F
Z kolei poniższy przykład generuje przewidywalne dane wyjściowe. Należy pamiętać, że Shared konstruktor klasy A nigdy nie jest wykonywany, mimo że klasa B pochodzi od niego:
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
Dane wyjściowe to:
Init B
B.G
Istnieje również możliwość konstruowania zależności cyklicznych, które umożliwiają Shared obserwowanie zmiennych za pomocą inicjatorów zmiennych w ich domyślnym stanie wartości, jak w poniższym przykładzie:
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
Spowoduje to wygenerowanie danych wyjściowych:
X = 1, Y = 2
Aby wykonać metodę, system najpierw ładuje klasę MainB.
Shared Konstruktor klasy B kontynuuje obliczanie początkowej Ywartości klasy , która rekursywnie powoduje załadowanie klasyA, ponieważ wartość A.X jest przywoływany. Konstruktor Shared klasy A z kolei przechodzi do obliczenia początkowej wartości X, a w ten sposób pobiera wartośćYdomyślną , która jest równa zero.
A.X jest zatem inicjowany do 1. Następnie proces ładowania A kończy się, wracając do obliczenia początkowej Ywartości , którego wynikiem staje się 2.
Gdyby metoda zamiast tego Main znajdowała się w klasie A, przykład wygenerowałby następujące dane wyjściowe:
X = 2, Y = 1
Unikaj odwołań cyklicznych w Shared inicjatorach zmiennych, ponieważ zwykle nie można określić kolejności ładowania klas zawierających takie odwołania.
Zdarzenia
Zdarzenia są używane do powiadamiania kodu o konkretnym wystąpieniu. Deklaracja zdarzenia składa się z identyfikatora, typu delegata lub listy parametrów oraz klauzuli opcjonalnej Implements .
EventMemberDeclaration
: RegularEventMemberDeclaration
| CustomEventMemberDeclaration
;
RegularEventMemberDeclaration
: Attributes? EventModifiers* 'Event'
Identifier ParametersOrType ImplementsClause? StatementTerminator
;
InterfaceEventMemberDeclaration
: Attributes? InterfaceEventModifiers* 'Event'
Identifier ParametersOrType StatementTerminator
;
ParametersOrType
: ( OpenParenthesis ParameterList? CloseParenthesis )?
| 'As' NonArrayTypeName
;
EventModifiers
: AccessModifier
| 'Shadows'
| 'Shared'
;
InterfaceEventModifiers
: 'Shadows'
;
Jeśli określono typ delegata, typ delegata może nie mieć typu zwracanego. Jeśli zostanie określona lista parametrów, może nie zawierać Optional ani ParamArray parametrów. Domena ułatwień dostępu typów parametrów i/lub typu delegata musi być taka sama jak lub nadzbiór samej domeny ułatwień dostępu samego zdarzenia. Zdarzenia mogą być współużytkowane przez określenie Shared modyfikatora.
Oprócz nazwy elementu członkowskiego dodanego do przestrzeni deklaracji typu, deklaracja zdarzenia niejawnie deklaruje kilka innych elementów członkowskich. Biorąc pod uwagę zdarzenie o nazwie X, następujące elementy członkowskie są dodawane do przestrzeni deklaracji:
Jeśli forma deklaracji jest deklaracją metody, zostanie wprowadzona zagnieżdżona klasa delegata o nazwie
XEventHandler. Zagnieżdżona klasa delegata jest zgodna z deklaracją metody i ma taką samą dostępność jak zdarzenie. Atrybuty na liście parametrów mają zastosowanie do parametrów klasy delegata.Zmienna
Privatewystąpienia typowana jako delegat o nazwieXEvent.Dwie metody o nazwie
add_Xiremove_Xktórych nie można wywołać, przesłonięć ani przeciążyć.
Jeśli typ próbuje zadeklarować nazwę zgodną z jedną z powyższych nazw, zostanie wyświetlony błąd czasu kompilacji, a niejawne add_X i remove_X deklaracje są ignorowane na potrzeby powiązania nazwy. Nie można przesłonić ani przeciążyć żadnego z wprowadzonych elementów członkowskich, chociaż istnieje możliwość zaciemnienia ich w typach pochodnych. Na przykład deklaracja klasy
Class Raiser
Public Event Constructed(i As Integer)
End Class
jest odpowiednikiem następującej deklaracji
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
Deklarowanie zdarzenia bez określania typu delegata jest najprostszą i najbardziej kompaktową składnią, ale ma wadę deklarowania nowego typu delegata dla każdego zdarzenia. Na przykład w poniższym przykładzie są tworzone trzy ukryte typy delegatów, mimo że wszystkie trzy zdarzenia mają tę samą listę parametrów:
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
W poniższym przykładzie zdarzenia po prostu używają tego samego delegata: 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
Zdarzenia mogą być obsługiwane na jeden z dwóch sposobów: statycznie lub dynamicznie. Statyczne obsługa zdarzeń jest prostsza i wymaga tylko zmiennej WithEvents i klauzuli Handles . W poniższym przykładzie klasa Form1 statycznie obsługuje zdarzenie Click obiektu Button:
Public Class Form1
Public WithEvents Button1 As New Button()
Public Sub Button1_Click(sender As Object, e As EventArgs) _
Handles Button1.Click
Console.WriteLine("Button1 was clicked!")
End Sub
End Class
Dynamiczne obsługa zdarzeń jest bardziej złożona, ponieważ zdarzenie musi być jawnie połączone i odłączone od kodu.
AddHandler Instrukcja dodaje procedurę obsługi dla zdarzenia, a instrukcja RemoveHandler usuwa procedurę obsługi dla zdarzenia. W następnym przykładzie przedstawiono klasęForm1, która dodaje Button1_Click jako procedurę obsługi zdarzeń dla Button1zdarzenia :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
W metodzie Disconnectprogram obsługi zdarzeń jest usuwany.
Zdarzenia niestandardowe
Jak wspomniano w poprzedniej sekcji, deklaracje zdarzeń niejawnie definiują pole, metodę add_ i remove_ metodę, która jest używana do śledzenia procedur obsługi zdarzeń. Jednak w niektórych sytuacjach może być pożądane udostępnienie niestandardowego kodu do śledzenia procedur obsługi zdarzeń. Jeśli na przykład klasa definiuje czterdzieści zdarzeń, z których tylko kilka będzie kiedykolwiek obsługiwanych, użycie tabeli skrótu zamiast czterdziestu pól do śledzenia procedur obsługi dla każdego zdarzenia może być bardziej wydajne.
Zdarzenia niestandardowe umożliwiają add_X jawne zdefiniowanie metod i remove_X , co umożliwia używanie magazynu niestandardowego dla programów obsługi zdarzeń.
Zdarzenia niestandardowe są deklarowane w taki sam sposób, że zdarzenia określające typ delegata są deklarowane z wyjątkiem, że słowo kluczowe musi poprzedzać Event słowo kluczoweCustom. Niestandardowa deklaracja zdarzenia zawiera trzy deklaracje: deklarację AddHandler , deklarację RemoveHandler i deklarację RaiseEvent . Żadna z deklaracji nie może mieć żadnych modyfikatorów, chociaż mogą mieć atrybuty.
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
;
Przykład:
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
Deklaracja AddHandler i RemoveHandler ma jeden ByVal parametr, który musi być typu delegata zdarzenia. Po wykonaniu AddHandler instrukcji lub RemoveHandler (lub klauzula Handles automatycznie obsługuje zdarzenie), zostanie wywołana odpowiednia deklaracja. Deklaracja RaiseEvent przyjmuje te same parametry co delegat zdarzenia i będzie wywoływana po wykonaniu RaiseEvent instrukcji. Wszystkie deklaracje muszą być podane i są uważane za podrouty.
Należy pamiętać, że AddHandlerRemoveHandler deklaracje i RaiseEvent mają to samo ograniczenie dotyczące umieszczania wierszy, które mają podrouty. Instrukcja początkowa, instrukcja end i blok muszą być wyświetlane na początku wiersza logicznego.
Oprócz nazwy elementu członkowskiego dodanego do przestrzeni deklaracji typu, niestandardowa deklaracja zdarzenia niejawnie deklaruje kilka innych elementów członkowskich. Biorąc pod uwagę zdarzenie o nazwie X, następujące elementy członkowskie są dodawane do przestrzeni deklaracji:
Metoda o nazwie
add_X, odpowiadająca deklaracjiAddHandler.Metoda o nazwie
remove_X, odpowiadająca deklaracjiRemoveHandler.Metoda o nazwie
fire_X, odpowiadająca deklaracjiRaiseEvent.
Jeśli typ próbuje zadeklarować nazwę zgodną z jedną z powyższych nazw, zostanie wyświetlony błąd czasu kompilacji, a deklaracje niejawne są ignorowane na potrzeby powiązania nazwy. Nie można przesłonić ani przeciążyć żadnego z wprowadzonych elementów członkowskich, chociaż istnieje możliwość zaciemnienia ich w typach pochodnych.
Uwaga.
Custom nie jest słowem zastrzeżonym.
Zdarzenia niestandardowe w zestawach WinRT
Począwszy od programu Microsoft Visual Basic 11.0 zdarzenia zadeklarowane w pliku skompilowanym z elementem /target:winmdobjlub zadeklarowane w interfejsie w takim pliku, a następnie zaimplementowane w innym miejscu, są traktowane nieco inaczej.
Narzędzia zewnętrzne używane do tworzenia winmd zwykle zezwalają tylko na niektóre typy delegatów, takie jak
System.EventHandler(Of T)lub ,System.TypedEventHandle(Of T, U)i nie zezwalają innym.Pole
XEventma typSystem.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenTable(Of T), gdzieTjest typem delegata.Metoda dostępu AddHandler zwraca metodę
System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken, a metoda dostępu RemoveHandler przyjmuje jeden parametr tego samego typu.
Oto przykład takiego zdarzenia niestandardowego.
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
Stałe
Stała to stała wartość, która jest elementem członkowskim typu.
ConstantMemberDeclaration
: Attributes? ConstantModifier* 'Const' ConstantDeclarators StatementTerminator
;
ConstantModifier
: AccessModifier
| 'Shadows'
;
ConstantDeclarators
: ConstantDeclarator ( Comma ConstantDeclarator )*
;
ConstantDeclarator
: Identifier ( 'As' TypeName )? Equals ConstantExpression StatementTerminator
;
Stałe są niejawnie udostępniane. Jeśli deklaracja zawiera klauzulę As , klauzula określa typ elementu członkowskiego wprowadzonego przez deklarację. Jeśli typ zostanie pominięty, typ stałej zostanie wywnioskowany. Typ stałej może być tylko typem pierwotnym lub Object. Jeśli stała jest typowana jako Object i nie ma znaku typu, rzeczywisty typ stałej będzie typem wyrażenia stałego. W przeciwnym razie typ stałej jest typem znaku typu stałej.
W poniższym przykładzie pokazano klasę o nazwie Constants , która ma dwie stałe publiczne:
Class Constants
Public Const A As Integer = 1
Public Const B As Integer = A + 1
End Class
Dostęp do stałych można uzyskać za pośrednictwem klasy, jak w poniższym przykładzie, który wyświetla wartości Constants.A i Constants.B.
Module Test
Sub Main()
Console.WriteLine(Constants.A & ", " & Constants.B)
End Sub
End Module
Stała deklaracja, która deklaruje wiele stałych, jest równoważna wielokrotnym deklaracjom pojedynczych stałych. Poniższy przykład deklaruje trzy stałe w jednej instrukcji deklaracji.
Class A
Protected Const x As Integer = 1, y As Long = 2, z As Short = 3
End Class
Ta deklaracja jest równoważna następującym:
Class A
Protected Const x As Integer = 1
Protected Const y As Long = 2
Protected Const z As Short = 3
End Class
Domena ułatwień dostępu typu stałej musi być taka sama jak lub nadzbiór domeny ułatwień dostępu samej stałej. Wyrażenie stałe musi zwracać wartość typu stałej lub typu niejawnie konwertowanego na typ stałej. Wyrażenie stałe może nie być cykliczne; oznacza to, że stała może nie być zdefiniowana w kategoriach samych siebie.
Kompilator automatycznie ocenia deklaracje stałe w odpowiedniej kolejności. W poniższym przykładzie kompilator najpierw oblicza Ywartości , a następnie Z, i na koniec X, generując odpowiednio wartości 10, 11 i 12.
Class A
Public Const X As Integer = B.Z + 1
Public Const Y As Integer = 10
End Class
Class B
Public Const Z As Integer = A.Y + 1
End Class
Gdy żądana jest nazwa symboliczna dla wartości stałej, ale typ wartości nie jest dozwolony w deklaracji stałej lub gdy nie można obliczyć wartości w czasie kompilacji przez wyrażenie stałe, można zamiast tego użyć zmiennej tylko do odczytu.
Wystąpienia i zmienne udostępnione
Wystąpienie lub zmienna udostępniona jest elementem członkowskim typu, który może przechowywać informacje.
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
;
Modyfikator Dim musi być określony, jeśli nie określono modyfikatorów, ale może zostać pominięty w przeciwnym razie. Pojedyncza deklaracja zmiennej może zawierać wiele deklaratorów zmiennych; każdy deklarator zmiennych wprowadza nowe wystąpienie lub współużytkowany element członkowski.
W przypadku określenia inicjatora można zadeklarować tylko jedno wystąpienie lub zmienną udostępnioną przez deklaratora zmiennych:
Class Test
Dim a, b, c, d As Integer = 10 ' Invalid: multiple initialization
End Class
To ograniczenie nie ma zastosowania do inicjatorów obiektów:
Class Test
Dim a, b, c, d As New Collection() ' OK
End Class
Zmienna zadeklarowana za pomocą Shared modyfikatora jest zmienną współdzieloną. Współdzielona zmienna identyfikuje dokładnie jedną lokalizację magazynu niezależnie od liczby wystąpień typu, które są tworzone. Współdzielona zmienna pojawia się, gdy program rozpoczyna wykonywanie i przestaje istnieć po zakończeniu działania programu.
Współdzielona zmienna jest współdzielona tylko między wystąpieniami określonego zamkniętego typu ogólnego. Na przykład program:
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
Wydruki:
1
1
2
Zmienna zadeklarowana bez Shared modyfikatora jest nazywana zmienną wystąpienia. Każde wystąpienie klasy zawiera oddzielną kopię wszystkich zmiennych wystąpienia klasy. Zmienna wystąpienia typu odwołania pojawia się w momencie utworzenia nowego wystąpienia tego typu i przestaje istnieć, gdy nie ma żadnych odwołań do tego wystąpienia, a Finalize metoda została wykonana. Zmienna wystąpienia typu wartości ma dokładnie taki sam okres istnienia, jak zmienna, do której należy. Innymi słowy, gdy zmienna typu wartości istnieje lub przestaje istnieć, więc zmienna wystąpienia typu wartości.
Jeśli deklarator zawiera klauzulę As , klauzula określa typ elementów członkowskich wprowadzonych przez deklarację. Jeśli typ zostanie pominięty, a używane są ścisłe semantyka, wystąpi błąd czasu kompilacji. W przeciwnym razie typ składowych jest niejawnie Object lub typ znaku typu składowych.
Uwaga. Nie ma niejednoznaczności w składni: jeśli deklarator pomija typ, zawsze będzie używać typu następującego deklaratora.
Domena ułatwień dostępu typu wystąpienia lub typu elementu tablicy lub zmiennej udostępnionej musi być taka sama jak lub nadzbiór domeny ułatwień dostępu wystąpienia lub samej zmiennej udostępnionej.
W poniższym przykładzie przedstawiono klasę Color z wewnętrznymi zmiennymi wystąpienia o nazwie redPart, greenParti bluePart:
Class Color
Friend redPart As Short
Friend bluePart As Short
Friend greenPart As Short
Public Sub New(red As Short, blue As Short, green As Short)
redPart = red
bluePart = blue
greenPart = green
End Sub
End Class
zmienne Read-Only
Gdy wystąpienie lub deklaracja zmiennej współużytkowanej zawiera ReadOnly modyfikator, przypisania do zmiennych wprowadzonych przez deklarację mogą występować tylko w ramach deklaracji lub w konstruktorze w tej samej klasie. W szczególności przypisania do wystąpienia tylko do odczytu lub zmiennej udostępnionej są dozwolone tylko w następujących sytuacjach:
W deklaracji zmiennej, która wprowadza wystąpienie lub zmienną udostępnioną (przez dołączenie inicjatora zmiennej w deklaracji).
Dla zmiennej wystąpienia w konstruktorach wystąpień klasy zawierającej deklarację zmiennej. Dostęp do zmiennej wystąpienia można uzyskać tylko w sposób niekwalifikowany lub za pośrednictwem
MelubMyClass.W przypadku zmiennej udostępnionej w konstruktorze udostępnionym klasy zawierającej deklarację zmiennej udostępnionej.
Współużytkowana zmienna tylko do odczytu jest przydatna, gdy żądana jest nazwa symboliczna stałej wartości, ale gdy typ wartości nie jest dozwolony w deklaracji stałej lub gdy wartość nie może być obliczana w czasie kompilacji przez wyrażenie stałe.
Przykład pierwszej takiej aplikacji jest następujący, w którym zadeklarowane ReadOnly są zmienne współużytkowane koloru, aby zapobiec ich zmianie przez inne programy:
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
Stałe i zmienne udostępnione tylko do odczytu mają różne semantyki. Gdy wyrażenie odwołuje się do stałej, wartość stałej jest uzyskiwana w czasie kompilacji, ale gdy wyrażenie odwołuje się do zmiennej udostępnionej tylko do odczytu, wartość zmiennej udostępnionej nie jest uzyskiwana do czasu wykonywania. Rozważmy następującą aplikację, która składa się z dwóch oddzielnych programów.
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
Przestrzenie Program1 nazw i Program2 oznaczają dwa programy, które są kompilowane oddzielnie. Ponieważ zmienna Program1.Utils.X jest zadeklarowana jako Shared ReadOnly, wartość wyjściowa instrukcji Console.WriteLine nie jest znana w czasie kompilacji, ale raczej jest uzyskiwana w czasie wykonywania. W związku z tym, jeśli wartość X elementu zostanie zmieniona i Program1 zostanie ponownie skompilowana, instrukcja zwróci nową wartość, Console.WriteLine nawet jeśli Program2 nie zostanie ponownie skompilowana. Jednak gdyby X była stała, wartość X elementu zostałaby uzyskana w czasie Program2 kompilacji i pozostałaby nienaruszona przez zmiany do Program1 momentu Program2 ponownego skompilowania.
Zmienne WithEvents
Typ może zadeklarować, że obsługuje zestaw zdarzeń zgłaszanych przez jedno z jego wystąpień lub zmiennych udostępnionych, deklarując wystąpienie lub zmienną udostępnioną, która zgłasza zdarzenia modyfikatorowi WithEvents . Przykład:
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
W tym przykładzie metoda E1Handler obsługuje zdarzenie E1 wywoływane przez wystąpienie typu Raiser przechowywanego w zmiennej xwystąpienia .
Modyfikator WithEvents powoduje zmianę nazwy zmiennej na wiodące podkreślenie i zastąpioną właściwością o tej samej nazwie, która wykonuje podpięcie zdarzenia. Jeśli na przykład nazwa zmiennej to F, zostanie zmieniona na _F , a właściwość F jest niejawnie zadeklarowana. Jeśli wystąpi kolizja między nową nazwą zmiennej a inną deklaracją, zostanie zgłoszony błąd czasu kompilacji. Wszystkie atrybuty zastosowane do zmiennej są przenoszone do zmiennej o zmienionej nazwie.
Niejawna właściwość utworzona przez deklarację WithEvents zajmuje się łączeniem i odłączanie odpowiednich procedur obsługi zdarzeń. Gdy wartość jest przypisana do zmiennej, właściwość najpierw wywołuje remove metodę zdarzenia w wystąpieniu aktualnie w zmiennej (cofając istniejącą procedurę obsługi zdarzeń, jeśli istnieje). Następnie przypisanie zostanie wykonane, a właściwość wywołuje add metodę zdarzenia w nowym wystąpieniu w zmiennej (podłączając nową procedurę obsługi zdarzeń). Poniższy kod jest odpowiednikiem powyższego kodu dla modułu Teststandardowego :
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
Nieprawidłowe deklarowanie wystąpienia lub zmiennej udostępnionej tak, jakby WithEvents zmienna była typowana jako struktura. Ponadto WithEvents nie można określić struktury i WithEventsReadOnly nie można jej połączyć.
Inicjatory zmiennych
Deklaracje wystąpień i zmiennych współużytkowanych w klasach i deklaracjach zmiennych wystąpień (ale nie deklaracje zmiennych udostępnionych) w strukturach mogą zawierać inicjatory zmiennych. W przypadku Shared zmiennych inicjatory zmiennych odpowiadają instrukcjom przypisania wykonywanym po rozpoczęciu programu, ale przed pierwszym odwołaniem do zmiennej Shared . Na przykład zmienne inicjatory zmiennych odpowiadają instrukcjom przypisania wykonywanym podczas tworzenia wystąpienia klasy. Struktury nie mogą mieć inicjatorów zmiennych wystąpień, ponieważ nie można modyfikować ich konstruktorów bez parametrów.
Rozważmy następujący przykład:
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
W przykładzie są generowane następujące dane wyjściowe:
x = 1.4142135623731, i = 100, s = Hello
Przypisanie ma x miejsce, gdy klasa jest ładowana, a przypisania do i i s wykonywane po utworzeniu nowego wystąpienia klasy.
Warto traktować inicjatory zmiennych jako instrukcje przypisania, które są automatycznie wstawiane do bloku konstruktora typu. Poniższy przykład zawiera kilka inicjatorów zmiennych wystąpień.
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
Przykład odpowiada kodowi pokazanym poniżej, gdzie każdy komentarz wskazuje automatycznie wstawioną instrukcję.
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
Wszystkie zmienne są inicjowane do wartości domyślnej ich typu przed wykonaniem dowolnych inicjatorów zmiennych. Przykład:
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
Ponieważ b jest automatycznie inicjowany do wartości domyślnej, gdy klasa jest ładowana i i jest automatycznie inicjowana do jej wartości domyślnej po utworzeniu wystąpienia klasy, powyższy kod generuje następujące dane wyjściowe:
b = False, i = 0
Każdy inicjator zmiennej musi zwracać wartość typu zmiennej lub typu niejawnie konwertowanego na typ zmiennej. Inicjator zmiennej może być okrągły lub odwoływać się do zmiennej, która zostanie zainicjowana po niej, w takim przypadku wartość zmiennej, do której odwołuje się odwołanie, jest jego wartością domyślną dla celów inicjatora. Taki inicjator ma wątpliwą wartość.
Istnieją trzy formy inicjatorów zmiennych: zwykłe inicjatory, inicjatory rozmiaru tablicy i inicjatory obiektów. Pierwsze dwa formularze pojawiają się po znaku równości, który jest zgodny z nazwą typu, dwa ostatnie są częścią samej deklaracji. Tylko jedna forma inicjatora może być używana w dowolnej określonej deklaracji.
Zwykłe inicjatory
Zwykły inicjator to wyrażenie niejawnie konwertowane na typ zmiennej. Pojawia się on po znaku równości, który jest zgodny z nazwą typu i musi zostać sklasyfikowany jako wartość. Przykład:
Module Test
Dim x As Integer = 10
Dim y As Integer = 20
Sub Main()
Console.WriteLine("x = " & x & ", y = " & y)
End Sub
End Module
Ten program generuje dane wyjściowe:
x = 10, y = 20
Jeśli deklaracja zmiennej ma zwykły inicjator, wówczas można zadeklarować tylko jedną zmienną naraz. Przykład:
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
Inicjatory obiektów
Inicjator obiektu jest określany przy użyciu wyrażenia tworzenia obiektu w miejscu nazwy typu. Inicjator obiektu jest odpowiednikiem zwykłego inicjatora, który przypisuje wynik wyrażenia tworzenia obiektu do zmiennej. Więc
Module TestModule
Sub Main()
Dim x As New Test(10)
End Sub
End Module
jest równoważny
Module TestModule
Sub Main()
Dim x As Test = New Test(10)
End Sub
End Module
Nawias w inicjatorze obiektu jest zawsze interpretowany jako lista argumentów konstruktora i nigdy jako modyfikatory typów tablicy. Nazwa zmiennej z inicjatorem obiektu nie może mieć modyfikatora typu tablicy ani modyfikatora typu dopuszczającego wartość null.
inicjatory Array-Size
Inicjator rozmiaru tablicy to modyfikator nazwy zmiennej, która daje zestaw wierzchnich granic wymiarów oznaczonych wyrażeniami.
ArraySizeInitializationModifier
: OpenParenthesis BoundList CloseParenthesis ArrayTypeModifiers?
;
BoundList
: Bound ( Comma Bound )*
;
Bound
: Expression
| '0' 'To' Expression
;
Wyrażenia górnej granicy muszą być klasyfikowane jako wartości i muszą być niejawnie konwertowane na Integer. Zestaw wyższej granicy jest odpowiednikiem inicjatora zmiennej wyrażenia tworzenia tablicy z podanymi górnymi granicami. Liczba wymiarów typu tablicy jest wywnioskowana z inicjatora rozmiaru tablicy. Więc
Module Test
Sub Main()
Dim x(5, 10) As Integer
End Sub
End Module
jest równoważny
Module Test
Sub Main()
Dim x As Integer(,) = New Integer(5, 10) {}
End Sub
End Module
Wszystkie górne granice muszą być równe lub większe niż -1, a wszystkie wymiary muszą mieć określoną górną granicę. Jeśli typ elementu inicjowanej tablicy jest typem tablicy, modyfikatory typu tablicy przechodzą po prawej stronie inicjatora rozmiaru tablicy. Na przykład
Module Test
Sub Main()
Dim x(5,10)(,,) As Integer
End Sub
End Module
deklaruje zmienną x lokalną, której typem jest dwuwymiarowa tablica trójwymiarowych Integertablic z , zainicjowana do tablicy z granicami 0..5 w pierwszym wymiarze i 0..10 w drugim wymiarze. Nie można użyć inicjatora rozmiaru tablicy, aby zainicjować elementy zmiennej, której typem jest tablica tablic.
Deklaracja zmiennej z inicjatorem rozmiaru tablicy nie może mieć modyfikatora typu tablicy na jego typ lub zwykły inicjator.
Klasy System.MarshalByRefObject
Klasy pochodzące z klasy System.MarshalByRefObject są marshalowane przez granice kontekstu przy użyciu serwerów proxy (czyli według odwołania), a nie przez kopiowanie (czyli według wartości). Oznacza to, że wystąpienie takiej klasy może nie być prawdziwym wystąpieniem, ale zamiast tego może być tylko wycinką, która marshaluje dostęp do zmiennych i wywołań metod w granicach kontekstu.
W związku z tym nie można utworzyć odwołania do lokalizacji przechowywania zmiennych zdefiniowanych w takich klasach. Oznacza to, że zmienne typizowane jako klasy pochodzące z System.MarshalByRefObject klasy nie mogą być przekazywane do parametrów referencyjnych, a metody i zmienne zmiennych typowanych jako typy wartości mogą nie być dostępne. Zamiast tego język Visual Basic traktuje zmienne zdefiniowane na takich klasach, jakby były właściwościami (ponieważ ograniczenia są takie same we właściwościach).
Istnieje jeden wyjątek od tej reguły: element członkowski niejawnie lub jawnie kwalifikowany jest Me wykluczony z powyższych ograniczeń, ponieważ Me zawsze ma gwarancję, że jest obiektem rzeczywistym, a nie serwerem proxy.
Właściwości
Właściwości są naturalnym rozszerzeniem zmiennych; oba są nazwanymi elementami członkowskimi skojarzonymi typami, a składnia uzyskiwania dostępu do zmiennych i właściwości jest taka sama. W przeciwieństwie do zmiennych właściwości nie oznaczają jednak lokalizacji przechowywania. Zamiast tego właściwości mają metody dostępu, które określają instrukcje do wykonania w celu odczytania lub zapisania ich wartości.
Właściwości są definiowane za pomocą deklaracji właściwości. Pierwsza część deklaracji właściwości przypomina deklarację pola. Druga część zawiera Get akcesorium i/lub Set akcesorium.
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
;
W poniższym Button przykładzie klasa definiuje Caption właściwość.
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
Na podstawie powyższej Button klasy poniżej przedstawiono przykład użycia Caption właściwości :
Dim okButton As Button = New Button()
okButton.Caption = "OK" ' Invokes Set accessor.
Dim s As String = okButton.Caption ' Invokes Get accessor.
Set W tym miejscu metodę dostępu jest wywoływana przez przypisanie wartości do właściwości, a Get akcesor jest wywoływany przez odwoływanie się do właściwości w wyrażeniu.
Jeśli dla właściwości nie określono żadnego typu, a używana jest ścisła semantyka, wystąpi błąd czasu kompilacji; w przeciwnym razie typ właściwości jest niejawnie Object lub typ znaku typu właściwości. Deklaracja właściwości może zawierać metodę Get dostępu, która pobiera wartość właściwości, Set metodę dostępu, która przechowuje wartość właściwości lub oba te elementy. Ponieważ właściwość niejawnie deklaruje metody, właściwość może być zadeklarowana przy użyciu tych samych modyfikatorów co metoda. Jeśli właściwość jest zdefiniowana w interfejsie lub zdefiniowana za pomocą MustOverride modyfikatora, treść właściwości i End konstrukcja muszą zostać pominięte; w przeciwnym razie wystąpi błąd czasu kompilacji.
Lista parametrów indeksu składa się z sygnatury właściwości, więc właściwości mogą być przeciążone na parametrach indeksu, ale nie na typ właściwości. Lista parametrów indeksu jest taka sama jak w przypadku metody regularnej. Jednak żaden z parametrów nie może być modyfikowany za pomocą ByRef modyfikatora i żaden z nich nie może być nazwany Value (który jest zarezerwowany dla niejawnego parametru wartości w metodzie Set dostępu).
Właściwość może być zadeklarowana w następujący sposób:
Jeśli właściwość nie określa modyfikatora typu właściwości, właściwość musi mieć zarówno
Getmetodę dostępu, jak i metodęSetdostępu. Właściwość jest mówi się, że jest właściwością read-write.Jeśli właściwość określa
ReadOnlymodyfikator, właściwość musi mieć metodęGetdostępu i może nie miećSetdostępu. Właściwość jest uważana za właściwość tylko do odczytu. Błąd w czasie kompilacji występuje, jeśli właściwość tylko do odczytu jest celem przypisania.Jeśli właściwość określa
WriteOnlymodyfikator, właściwość musi mieć metodęSetdostępu i może nie miećGetdostępu. Mówi się, że właściwość jest właściwością tylko do zapisu. Jest to błąd czasu kompilacji, aby odwołać się do właściwości tylko do zapisu w wyrażeniu, z wyjątkiem jako elementu docelowego przypisania lub jako argumentu do metody.
Metody Get i Set właściwości nie są odrębnymi elementami członkowskimi i nie można zadeklarować akcesoriów właściwości oddzielnie. Poniższy przykład nie deklaruje pojedynczej właściwości odczytu i zapisu. Zamiast tego deklaruje dwie właściwości o tej samej nazwie: jeden tylko do odczytu i tylko do zapisu:
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
Ponieważ dwa elementy członkowskie zadeklarowane w tej samej klasie nie mogą mieć tej samej nazwy, przykład powoduje błąd czasu kompilacji.
Domyślnie dostępność właściwości Get i Set metod dostępu jest taka sama jak dostępność samej właściwości.
Get Jednak metody i Set mogą również określać dostępność oddzielnie od właściwości . W takim przypadku dostępność metody dostępu musi być bardziej restrykcyjna niż dostępność właściwości, a tylko jeden element dostępu może mieć inny poziom ułatwień dostępu od właściwości . Typy dostępu są uznawane za mniej lub bardziej restrykcyjne w następujący sposób:
Privatejest bardziej restrykcyjny niżPublic, ,Protected FriendProtectedlubFriend.Friendprogram jest bardziej restrykcyjny niżProtected FriendlubPublic.Protectedprogram jest bardziej restrykcyjny niżProtected FriendlubPublic.Protected Friendjest bardziej restrykcyjny niżPublic.
Gdy jeden z metod dostępu do właściwości jest dostępny, ale drugi nie, właściwość jest traktowana tak, jakby była tylko do odczytu lub tylko do zapisu. Przykład:
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
Gdy typ pochodny cieniuje właściwość, właściwość pochodna ukrywa właściwość w tle w odniesieniu zarówno do odczytu, jak i zapisu. W poniższym przykładzie właściwość w elemecie P ukrywa P właściwość w A odniesieniu do odczytu i B zapisu:
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
Domena ułatwień dostępu typu zwracanego lub typów parametrów musi być taka sama jak lub nadzbiór domeny ułatwień dostępu samej właściwości. Właściwość może mieć tylko jedną metodę dostępu i jedną SetGet metodę dostępu.
Z wyjątkiem różnic w składni deklaracji i wywołania, Overridablewłaściwości , , OverridesNotOverridable, MustOverridei MustInherit zachowują się dokładnie tak jak Overridable, , OverridesNotOverridable, , MustOverridei MustInherit metody. Gdy właściwość jest zastępowana, właściwość zastępowania musi być tego samego typu (tylko do odczytu i zapisu, tylko do odczytu, tylko do odczytu, tylko do zapisu). Właściwość Overridable nie może zawierać Private metody dostępu.
W poniższym przykładzie X jest Overridable właściwość tylko do odczytu, Y jest właściwością Overridable read-write i Z jest właściwością MustOverride read-write.
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
Ponieważ Z element to MustOverride, musi być zadeklarowana MustInheritklasa A zawierająca .
Natomiast poniżej przedstawiono klasę, która pochodzi z klasy A :
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
W tym miejscu deklaracje właściwości XYi Z przesłaniają właściwości podstawowe. Każda deklaracja właściwości dokładnie odpowiada modyfikatorom ułatwień dostępu, typowi i nazwie odpowiadającej jej właściwości dziedziczonej. Akcesorium Get właściwości X i Set metody dostępu do właściwości Y używa MyBase słowa kluczowego w celu uzyskania dostępu do odziedziczonych właściwości. Deklaracja właściwości Z zastępuje MustOverride właściwość — w związku z tym nie ma zaległych MustOverride składowych w klasie i B może być regularną klasąB.
Właściwości mogą służyć do opóźniania inicjowania zasobu do momentu pierwszego przywołowania. Przykład:
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
Klasa ConsoleStreams zawiera trzy właściwości, In, Outi Error, które reprezentują odpowiednio standardowe urządzenia wejściowe, wyjściowe i błędów. Uwidaczniając te elementy członkowskie jako właściwości, ConsoleStreams klasa może opóźnić ich inicjowanie, dopóki nie zostaną one rzeczywiście użyte. Na przykład podczas pierwszego odwoływania Out się do właściwości , jak w ConsoleStreams.Out.WriteLine("hello, world")pliku , element bazowy TextWriter dla urządzenia wyjściowego jest inicjowany. Jeśli jednak aplikacja nie odwołuje się do In właściwości i Error , dla tych urządzeń nie są tworzone żadne obiekty.
Uzyskiwanie deklaracji akcesoriów
Metodę Get dostępu (getter) jest deklarowana przy użyciu deklaracji właściwości Get . Deklaracja właściwości Get składa się ze słowa kluczowego Get , po którym następuje blok instrukcji. Biorąc pod uwagę właściwość o nazwie P, Get deklaracja metody dostępu niejawnie deklaruje metodę o nazwie get_P z tymi samymi modyfikatorami, typem i listą parametrów co właściwość. Jeśli typ zawiera deklarację o tej nazwie, wynik błędu czasu kompilacji, ale niejawna deklaracja jest ignorowana na potrzeby powiązania nazw.
Specjalna zmienna lokalna, która jest niejawnie zadeklarowana w Get przestrzeni deklaracji treści dostępu o takiej samej nazwie jak właściwość, reprezentuje wartość zwracaną właściwości. Zmienna lokalna ma semantykę rozpoznawania nazw specjalnych, gdy jest używana w wyrażeniach. Jeśli zmienna lokalna jest używana w kontekście, który oczekuje wyrażenia sklasyfikowanego jako grupa metod, takiego jak wyrażenie wywołania, nazwa jest rozpoznawana jako funkcja, a nie do zmiennej lokalnej. Przykład:
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
Użycie nawiasów może powodować niejednoznaczne sytuacje (takie jak F(1) gdzie F jest właściwością, której typem jest tablica jednowymiarowa). We wszystkich niejednoznacznych sytuacjach nazwa jest rozpoznawana jako właściwość, a nie zmienna lokalna. Przykład:
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
Gdy przepływ sterowania opuszcza Get treść metody dostępu, wartość zmiennej lokalnej jest przekazywana z powrotem do wyrażenia wywołania. Ponieważ wywoływanie Get metody dostępu jest koncepcyjnie równoważne odczytywaniu wartości zmiennej, jest uważane za nieprawidłowy styl Get programowania dla metod dostępu do obserwowanych skutków ubocznych, jak pokazano w poniższym przykładzie:
Class Counter
Private Value As Integer
Public ReadOnly Property NextValue() As Integer
Get
Value += 1
Return Value
End Get
End Property
End Class
Wartość NextValue właściwości zależy od liczby przypadków, w których wcześniej uzyskiwano dostęp do właściwości. W związku z tym uzyskanie dostępu do właściwości powoduje zauważalny efekt uboczny, a właściwość powinna zostać zaimplementowana jako metoda.
Konwencja "brak skutków ubocznych" dla Get metod dostępu nie oznacza, że Get metody dostępu powinny być zawsze zapisywane, aby po prostu zwracać wartości przechowywane w zmiennych.
Get W rzeczywistości metody dostępu często obliczają wartość właściwości przez uzyskanie dostępu do wielu zmiennych lub wywoływanie metod. Jednak prawidłowo zaprojektowane Get akcesorium nie wykonuje żadnych akcji, które powodują zauważalne zmiany w stanie obiektu.
Uwaga.
Get metody dostępu mają to samo ograniczenie w umieszczaniu linii, które mają podroutines. Instrukcja początkowa, instrukcja end i blok muszą być wyświetlane na początku wiersza logicznego.
PropertyGetDeclaration
: Attributes? AccessModifier? 'Get' LineTerminator
Block?
'End' 'Get' StatementTerminator
;
Ustawianie deklaracji dostępu
Metodę Set dostępu (setter) jest deklarowany przy użyciu deklaracji zestawu właściwości. Deklaracja zestawu właściwości składa się ze słowa kluczowego Set, opcjonalnej listy parametrów i bloku instrukcji. Biorąc pod uwagę właściwość o nazwie P, deklaracja ustawiająca niejawnie deklaruje metodę o nazwie set_P z tymi samymi modyfikatorami i listą parametrów co właściwość. Jeśli typ zawiera deklarację o tej nazwie, wynik błędu czasu kompilacji, ale niejawna deklaracja jest ignorowana na potrzeby powiązania nazw.
Jeśli określono listę parametrów, musi mieć jeden element członkowski, ten element członkowski nie musi mieć modyfikatorów z wyjątkiem ByVal, a jego typ musi być taki sam jak typ właściwości. Parametr reprezentuje ustawianą wartość właściwości. Jeśli parametr zostanie pominięty, parametr o nazwie Value jest niejawnie zadeklarowany.
Uwaga.
Set metody dostępu mają to samo ograniczenie w umieszczaniu linii, które mają podroutines. Instrukcja początkowa, instrukcja end i blok muszą być wyświetlane na początku wiersza logicznego.
PropertySetDeclaration
: Attributes? AccessModifier? 'Set'
( OpenParenthesis ParameterList? CloseParenthesis )? LineTerminator
Block?
'End' 'Set' StatementTerminator
;
Właściwości domyślne
Właściwość określająca modyfikator Default jest nazywana właściwością domyślną. Dowolny typ, który zezwala na właściwości, może mieć właściwość domyślną, w tym interfejsy. Do właściwości domyślnej można odwoływać się bez konieczności kwalifikowania wystąpienia z nazwą właściwości. W związku z tym, biorąc pod uwagę klasę
Class Test
Public Default ReadOnly Property Item(i As Integer) As Integer
Get
Return i
End Get
End Property
End Class
kod
Module TestModule
Sub Main()
Dim x As Test = New Test()
Dim y As Integer
y = x(10)
End Sub
End Module
jest równoważny
Module TestModule
Sub Main()
Dim x As Test = New Test()
Dim y As Integer
y = x.Item(10)
End Sub
End Module
Po zadeklarowaniu Defaultwłaściwości wszystkie właściwości przeciążone tą nazwą w hierarchii dziedziczenia stają się właściwością domyślną, niezależnie od tego, czy zostały zadeklarowane Default , czy nie. Deklarowanie właściwości Default w klasie pochodnej, gdy klasa bazowa zadeklarowała właściwość domyślną przez inną nazwę, nie wymaga żadnych innych modyfikatorów, takich jak Shadows lub Overrides. Dzieje się tak, ponieważ właściwość domyślna nie ma tożsamości ani podpisu, dlatego nie można jej zaciemniać ani przeciążyć. Przykład:
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
Ten program spowoduje wygenerowanie danych wyjściowych:
MoreDerived = 10
Derived = 10
Base = 10
Wszystkie właściwości domyślne zadeklarowane w obrębie typu muszą mieć taką samą nazwę, a w celu zapewnienia przejrzystości należy określić Default modyfikator. Ponieważ właściwość domyślna bez parametrów indeksu spowodowałaby niejednoznaczną sytuację podczas przypisywania wystąpień zawierającej klasy, właściwości domyślne muszą mieć parametry indeksu. Ponadto jeśli jedna właściwość przeciążona określoną nazwą zawiera Default modyfikator, wszystkie właściwości przeciążone tą nazwą muszą ją określić. Właściwości domyślne mogą nie mieć Sharedwartości , a co najmniej jeden element dostępu do właściwości nie może mieć wartości Private.
Automatycznie zaimplementowane właściwości
Jeśli właściwość pomija deklarację jakichkolwiek metod dostępu, implementacja właściwości zostanie podana automatycznie, chyba że właściwość zostanie zadeklarowana w interfejsie lub zostanie zadeklarowana MustOverride. Można automatycznie zaimplementować tylko właściwości odczytu/zapisu bez argumentów; w przeciwnym razie występuje błąd czasu kompilacji.
Automatycznie zaimplementowana właściwość x, nawet jedna przesłonięć inną właściwość, wprowadza prywatną zmienną _x lokalną o tym samym typie co właściwość. Jeśli wystąpi kolizja między nazwą zmiennej lokalnej a inną deklaracją, zostanie zgłoszony błąd czasu kompilacji. Metoda dostępu zaimplementowanej Get automatycznie zwraca wartość metody dostępu lokalnego i metody dostępu właściwości Set , która ustawia wartość lokalnego. Na przykład deklaracja:
Public Property x() As Integer
jest w przybliżeniu równoważne:
Private _x As Integer
Public Property x() As Integer
Get
Return _x
End Get
Set (value As Integer)
_x = value
End Set
End Property
Podobnie jak w przypadku deklaracji zmiennych, właściwość zaimplementowana automatycznie może zawierać inicjator. Przykład:
Public Property x() As Integer = 10
Public Shared Property y() As New Customer() With { .Name = "Bob" }
Uwaga. Gdy właściwość zaimplementowana automatycznie jest inicjowana, jest inicjowana za pośrednictwem właściwości, a nie za pomocą pola bazowego. Jest to więc przesłonięcia właściwości mogą przechwytywać inicjowanie, jeśli zajdzie taka potrzeba.
Inicjatory tablicy są dozwolone dla automatycznie zaimplementowanych właściwości, z tą różnicą, że nie ma możliwości jawnego określenia granic tablicy. Przykład:
' 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
Właściwości iteratora
Właściwość iteratora jest właściwością modyfikatoraIterator. Jest ona używana z tego samego powodu, dla której jest używana metoda iteratora (Metoda iteratora sekcji) — jako wygodny sposób generowania sekwencji, która może być używana przez instrukcję For Each . Metoda Get dostępu do właściwości iteratora jest interpretowana w taki sam sposób, jak metoda iteratora.
Właściwość iteratora musi mieć jawną Get metodę dostępu, a jej typ musi mieć IEnumeratorwartość , lub IEnumerablelub IEnumerator(Of T)IEnumerable(Of T) dla niektórych T.
Oto przykład właściwości iteratora:
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
Operatorów
Operatory to metody definiujące znaczenie istniejącego operatora języka Visual Basic dla klasy zawierającej. Gdy operator jest stosowany do klasy w wyrażeniu, operator jest kompilowany w wywołanie metody operatora zdefiniowanej w klasie. Definiowanie operatora dla klasy jest również nazywane przeciążeniem operatora.
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'
;
Nie można przeciążyć operatora, który już istnieje; w praktyce dotyczy to głównie operatorów konwersji. Na przykład nie można przeciążyć konwersji z klasy pochodnej do klasy bazowej:
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
Operatory mogą być również przeciążone w zdrowym znaczeniu słowa:
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
Deklaracje operatorów nie dodają jawnie nazw do przestrzeni deklaracji zawierającego typ; jednak niejawnie deklarują odpowiednią metodę rozpoczynającą się od znaków "op_". W poniższych sekcjach wymieniono odpowiednie nazwy metod z każdym operatorem.
Istnieją trzy klasy operatorów, które można zdefiniować: operatory jednoargumentowe, operatory binarne i operatory konwersji. Wszystkie deklaracje operatorów współdzielą pewne ograniczenia:
Deklaracje operatorów muszą zawsze mieć wartość
PubliciShared. ModyfikatorPublicmożna pominąć w kontekstach, w których zakłada się modyfikator.Nie można zadeklarować
ByRefparametrów operatora lubOptionalParamArray.Typ co najmniej jednego operandu lub zwracanej wartości musi być typem zawierającym operator .
Nie zdefiniowano zmiennej zwracanej funkcji dla operatorów.
ReturnW związku z tym instrukcja musi być używana do zwracania wartości z treści operatora.
Jedynym wyjątkiem od tych ograniczeń jest zastosowanie do typów wartości dopuszczanych do wartości null. Ponieważ typy wartości dopuszczalnych wartości null nie mają rzeczywistej definicji typu, typ wartości może zadeklarować operatory zdefiniowane przez użytkownika dla wersji dopuszczalnej wartości null typu. Podczas określania, czy typ może zadeklarować określony operator zdefiniowany przez użytkownika, ? modyfikatory są najpierw odrzucane ze wszystkich typów zaangażowanych w deklarację do celów sprawdzania ważności. To złagodzenie nie ma zastosowania do typu zwrotnego operatorów IsTrue i IsFalse ; muszą one nadal zwracać Boolean, a nie Boolean?.
Pierwszeństwo i kojarzenie operatora nie może być modyfikowane przez deklarację operatora.
Uwaga. Operatory mają to samo ograniczenie dotyczące umieszczania wierszy, które mają podrouty. Instrukcja początkowa, instrukcja end i blok muszą być wyświetlane na początku wiersza logicznego.
Operatory jednoargumentowe
Następujące operatory jednoargumentowe mogą być przeciążone:
Jednoargumentowy operator
+plus (odpowiednia metoda:op_UnaryPlus)Jednoargumentowy operator
-minus (odpowiadająca mu metoda:op_UnaryNegation)Operator logiczny
Not(odpowiadająca mu metoda:op_OnesComplement)Operatory
IsTrueiIsFalse(odpowiednie metody:op_True,op_False)
Wszystkie przeciążone operatory jednoargumentowe muszą mieć jeden parametr typu zawierającego i mogą zwracać dowolny typ, z wyjątkiem IsTrue i IsFalse, który musi zwrócić wartość Boolean. Jeśli typ zawierający jest typem ogólnym, parametry typu muszą być zgodne z parametrami typu zawierającego. Na przykład
Structure Complex
...
Public Shared Operator +(v As Complex) As Complex
Return v
End Operator
End Structure
Jeśli typ przeciąża jeden z IsTrue lub IsFalse, musi również przeciążyć drugą. Jeśli tylko jeden jest przeciążony, wynik błędu czasu kompilacji.
Uwaga.
IsTrue i IsFalse nie są słowami zarezerwowanymi.
Operatory binarne
Następujące operatory binarne mogą być przeciążone:
Dodawanie
+operatorów , odejmowania-, mnożenia*, dzielenia/, dzielenia\całkowitego , moduloModi wykładnika^(odpowiadającej metodzie:op_Addition,op_IntegerDivisionop_Subtractionop_Divisionop_Multiply, )op_Exponentop_ModulusOperatory
=relacyjne , ,<><>, ,<=>=(odpowiadające metody:op_Equality, ,op_Inequalityop_LessThan,op_GreaterThan, ,op_LessThanOrEqual,op_GreaterThanOrEqual). Uwaga. Mimo że operator równości może być przeciążony, operator przypisania (używany tylko w instrukcjach przypisania) nie może być przeciążony.Operator (odpowiadająca
Likemu metoda:op_Like)Operator
&łączenia (odpowiadająca mu metoda:op_Concatenate)Operatory logiczne
OrAndiXor(odpowiednie metody:op_BitwiseAnd,op_BitwiseOr,op_ExclusiveOr)Operatory
<<shift i>>(odpowiednie metody:op_LeftShift,op_RightShift)
Wszystkie przeciążone operatory binarne muszą przyjmować typ zawierający jako jeden z parametrów. Jeśli typ zawierający jest typem ogólnym, parametry typu muszą być zgodne z parametrami typu zawierającego. Operatory przesunięcia jeszcze bardziej ograniczają tę regułę, aby wymagać, aby pierwszy parametr był typu zawierającego; drugi parametr musi być zawsze typu Integer.
Następujące operatory binarne muszą być zadeklarowane w parach:
Operator
=i operator<>Operator
>i operator<Operator
>=i operator<=
Jeśli jedna z par jest zadeklarowana, druga musi być również zadeklarowana z pasującym parametrem i zwracanymi typami lub zostanie wyświetlony błąd czasu kompilacji. (Uwaga. Celem wymagania parowanych deklaracji operatorów relacyjnych jest próba zapewnienia co najmniej minimalnego poziomu spójności logicznej w przeciążonych operatorach).
W przeciwieństwie do operatorów relacyjnych przeciążenie zarówno operatorów dzielenia, jak i całkowitego dzielenia jest zdecydowanie odradzane, chociaż nie jest to błąd. (Uwaga. Ogólnie rzecz biorąc, dwa typy dzielenia powinny być całkowicie odrębne: typ obsługujący podział jest albo całkowity (w takim przypadku powinien obsługiwać \) lub nie (w takim przypadku powinien obsługiwać /). Rozważaliśmy błąd podczas definiowania obu operatorów, ale ponieważ ich języki zwykle nie rozróżniają dwóch typów dzielenia w sposób, w jaki program Visual Basic działa, uważamy, że jest najbezpieczniejsze, aby umożliwić praktykę, ale zdecydowanie zniechęcić do niej.
Operatory przypisania złożonego nie mogą być przeciążone bezpośrednio. Zamiast tego, gdy odpowiedni operator binarny jest przeciążony, operator przypisania złożonego użyje przeciążonego operatora. Przykład:
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
Operatory konwersji
Operatory konwersji definiują nowe konwersje między typami. Te nowe konwersje są nazywane konwersjami zdefiniowanymi przez użytkownika. Operator konwersji konwertuje z typu źródła wskazanego przez typ parametru operatora konwersji na typ docelowy wskazany przez typ zwracany operatora konwersji. Konwersje muszą być klasyfikowane jako rozszerzające lub zawężone. Deklaracja operatora konwersji zawierająca Widening słowo kluczowe wprowadza konwersję rozszerzającą zdefiniowaną przez użytkownika (odpowiadającą metodę: op_Implicit). Deklaracja operatora konwersji zawierająca Narrowing słowo kluczowe wprowadza zdefiniowaną przez użytkownika konwersję zawężającą (odpowiadającą metodę: op_Explicit).
Ogólnie rzecz biorąc, konwersje rozszerzające zdefiniowane przez użytkownika powinny być zaprojektowane tak, aby nigdy nie zgłaszać wyjątków i nigdy nie tracić informacji. Jeśli konwersja zdefiniowana przez użytkownika może powodować wyjątki (na przykład dlatego, że argument źródłowy jest poza zakresem) lub utrata informacji (takich jak odrzucanie bitów o wysokiej kolejności), konwersja ta powinna być zdefiniowana jako konwersja zawężania. W przykładzie:
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
konwersja z Digit na jest konwersją rozszerzającą, ponieważ nigdy nie zgłasza wyjątków lub traci informacje, ale konwersja z Byte na Digit jest konwersją zawężającą, ponieważ Digit może reprezentować tylko podzbiór możliwych wartości ByteByte .
W przeciwieństwie do wszystkich innych elementów członkowskich typu, które mogą być przeciążone, podpis operatora konwersji zawiera typ docelowy konwersji. Jest to jedyny element członkowski typu, dla którego zwracany typ uczestniczy w podpisie. Rozszerzająca lub zawężająca klasyfikacja operatora konwersji nie jest jednak częścią podpisu operatora. W związku z tym klasa lub struktura nie może zadeklarować zarówno operatora konwersji rozszerzającej, jak i operatora konwersji zawężającej z tymi samymi typami źródłowymi i docelowymi.
Operator konwersji zdefiniowanej przez użytkownika musi przekonwertować element na lub z typu zawierającego — na przykład istnieje możliwość, aby klasa C zdefiniowała konwersję z do i z CInteger do IntegerC, ale nie z Integer na Boolean. Jeśli typ zawierający jest typem ogólnym, parametry typu muszą być zgodne z parametrami typu zawierającego. Ponadto nie można ponownie zdefiniować konwersji wewnętrznej (tj. niezdefiniowanej przez użytkownika). W związku z tym typ nie może zadeklarować konwersji, w której:
Typ źródła i typ docelowy są takie same.
Zarówno typ źródłowy, jak i typ docelowy nie są typem definiującym operator konwersji.
Typ źródła lub typ docelowy jest typem interfejsu.
Typ źródła i typy docelowe są powiązane z dziedziczeniem (w tym
Object).
Jedynym wyjątkiem od tych reguł jest zastosowanie do typów wartości dopuszczanych do wartości null. Ponieważ typy wartości dopuszczanych do wartości null nie mają rzeczywistej definicji typu, typ wartości może zadeklarować konwersje zdefiniowane przez użytkownika dla wersji dopuszczanej do wartości null typu. Podczas określania, czy typ może zadeklarować określoną konwersję zdefiniowaną przez użytkownika, ? modyfikatory są najpierw odrzucane ze wszystkich typów zaangażowanych w deklarację do celów sprawdzania poprawności. W związku z tym następująca deklaracja jest prawidłowa, ponieważ S może zdefiniować konwersję z S na T:
Structure T
...
End Structure
Structure S
Public Shared Widening Operator CType(ByVal v As S?) As T
...
End Operator
End Structure
Następująca deklaracja nie jest jednak prawidłowa, ponieważ struktura S nie może zdefiniować konwersji z S na S:
Structure S
Public Shared Widening Operator CType(ByVal v As S) As S?
...
End Operator
End Structure
Mapowanie operatorów
Ponieważ zestaw operatorów obsługiwanych przez język Visual Basic może nie być dokładnie zgodny z zestawem operatorów innych języków w programie .NET Framework, niektóre operatory są mapowane specjalnie na inne operatory podczas definiowania lub użycia. Specyficznie:
Zdefiniowanie operatora dzielenia całkowitego automatycznie zdefiniuje zwykły operator dzielenia (dostępny tylko z innych języków), który będzie wywoływać operator dzielenia całkowitego.
Przeciążenie operatorów ,
AndiOrspowoduje przeciążenieNottylko operatora bitowego z perspektywy innych języków, które rozróżniają operatory logiczne i bitowe.Klasa, która przeciąża tylko operatory logiczne w języku, który rozróżnia operatory logiczne i bitowe (tj. języki używające
op_LogicalNotop_LogicalAndodpowiednio , iop_LogicalOrdlaAndNot, iOr) będą miały operatory logiczne mapowane na operatory logiczne Języka Visual Basic. Jeśli oba operatory logiczne i bitowe są przeciążone, będą używane tylko operatory bitowe.Przeciążenie operatorów i
>>spowoduje przeciążenie<<tylko podpisanych operatorów z perspektywy innych języków, które rozróżniają operatory przesunięcia ze znakiem i bez znaku.Klasa, która przeciąża tylko niepodpisany operator przesunięcia, będzie miał niepodpisany operator przesunięcia zamapowany na odpowiadający mu operator przesunięcia języka Visual Basic. Jeśli zarówno niepodpisany, jak i podpisany operator przesunięcia jest przeciążony, zostanie użyty tylko podpisany operator przesunięcia.
Visual Basic language spec