Składowe typu

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:

  • Overridable i NotOverridable wzajemnie się wykluczają i nie mogą być połączone.

  • MustOverride oznacza Overridable (i tak nie można go określić) i nie można go połączyć z NotOverridable.

  • NotOverridable nie można połączyć z Overridable lub MustOverride i musi być połączony z Overrides.

  • Overrides oznacza Overridable (i tak nie można go określić) i nie można go połączyć z MustOverride.

Istnieją również dodatkowe ograniczenia dotyczące metod zastępowalnych:

  • MustOverride Metoda może nie zawierać treści metody lub End konstrukcji, może nie zastąpić innej metody i może być wyświetlana tylko w MustInherit klasach.

  • Jeśli metoda określa Overrides i nie ma pasującej metody podstawowej do zastąpienia, wystąpi błąd czasu kompilacji. Metoda zastępowania może nie określać wartości Shadows.

  • 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 Friend metody w innym zestawie, który nie ma Friend dostępu, musi określić Protected (nie Protected Friend).

  • Private metody nie mogą być Overridablemetodami , NotOverridablelub MustOverride, ani nie mogą zastąpić innych metod.

  • Metody w NotInheritable klasach mogą nie być zadeklarowane Overridable lub MustOverride.

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 ParamArray może być pojedynczym wyrażeniem typu, które rozszerza typ ParamArray . W tym przypadku parametr ParamArray dział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 typ ParamArrayelementu . W tym przypadku wywołanie tworzy wystąpienie ParamArray typu 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 WithEvents modyfikator lub MyBase słowo kluczowe lub lub MyClassMe ; 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 Private wystąpienia typowana jako delegat o nazwie XEvent.

  • Dwie metody o nazwie add_X i remove_X któ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 deklaracji AddHandler .

  • Metoda o nazwie remove_X, odpowiadająca deklaracji RemoveHandler .

  • Metoda o nazwie fire_X, odpowiadająca deklaracji RaiseEvent .

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 XEvent ma typ System.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenTable(Of T) , gdzie T jest 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 Me lub MyClass.

  • 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 Get metodę dostępu, jak i metodę Set dostępu. Właściwość jest mówi się, że jest właściwością read-write.

  • Jeśli właściwość określa ReadOnly modyfikator, właściwość musi mieć metodę Get dostępu i może nie mieć Set dostę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 WriteOnly modyfikator, właściwość musi mieć metodę Set dostępu i może nie mieć Get dostę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 FriendProtectedlub Friend.

  • Friend program jest bardziej restrykcyjny niż Protected Friend lub Public.

  • Protected program jest bardziej restrykcyjny niż Protected Friend lub Public.

  • Protected Friend jest 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ść Public i Shared. Modyfikator Public można pominąć w kontekstach, w których zakłada się modyfikator.

  • Nie można zadeklarować ByRefparametrów operatora lub OptionalParamArray.

  • Typ co najmniej jednego operandu lub zwracanej wartości musi być typem zawierającym operator .

  • Nie zdefiniowano zmiennej zwracanej funkcji dla operatorów. Return W 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 IsTrue i IsFalse (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 , modulo Mod i wykładnika ^ (odpowiadającej metodzie: op_Addition, op_IntegerDivisionop_Subtractionop_Divisionop_Multiply, ) op_Exponentop_Modulus

  • Operatory =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 Like mu metoda: op_Like)

  • Operator & łączenia (odpowiadająca mu metoda: op_Concatenate)

  • Operatory logiczne OrAndi Xor (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 , Andi Or spowoduje przeciążenie Nottylko 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 , i op_LogicalOr dla AndNot, i Or) 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.