Type members define storage locations and executable code. They can be methods, constructors, events, constants, variables, and properties.
Methods, events, and properties can implement interface members. To implement an interface member, a member declaration specifies the Implements
keyword and lists one or more interface members.
ImplementsClause
: ( 'Implements' ImplementsList )?
;
ImplementsList
: InterfaceMemberSpecifier ( Comma InterfaceMemberSpecifier )*
;
InterfaceMemberSpecifier
: NonArrayTypeName Period IdentifierOrKeyword
;
Methods and properties that implement interface members are implicitly NotOverridable
unless declared to be MustOverride
, Overridable
, or overriding another member. It is an error for a member implementing an interface member to be Shared
. A member's accessibility has no effect on its ability to implement interface members.
For an interface implementation to be valid, the implements list of the containing type must name an interface that contains a compatible member. A compatible member is one whose signature matches the signature of the implementing member. If a generic interface is being implemented, then the type argument supplied in the Implements clause is substituted into the signature when checking compatibility. For example:
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
If an event declared using a delegate type is implementing an interface event, then a compatible event is one whose underlying delegate type is the same type. Otherwise, the event uses the delegate type from the interface event it is implementing. If such an event implements multiple interface events, all the interface events must have the same underlying delegate type. For example:
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
An interface member in the implements list is specified using a type name, a period, and an identifier. The type name must be an interface in the implements list or a base interface of an interface in the implements list, and the identifier must be a member of the specified interface. A single member can implement more than one matching interface member.
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
If the interface member being implemented is unavailable in all explicitly implemented interfaces because of multiple interface inheritance, the implementing member must explicitly reference a base interface on which the member is available. For example, if I1
and I2
contain a member M
, and I3
inherits from I1
and I2
, a type implementing I3
will implement I1.M
and I2.M
. If an interface shadows multiply inherited members, an implementing type will have to implement the inherited members and the member(s) shadowing them.
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
If the containing interface of the interface member be implemented is generic, the same type arguments as the interface being implements must be supplied. For example:
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
Methods contain the executable statements of a program.
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
;
Methods, which have an optional list of parameters and an optional return value, are either shared or not shared. Shared methods are accessed through the class or instances of the class. Non-shared methods, also called instance methods, are accessed through instances of the class. The following example shows a class Stack
that has several shared methods (Clone
and Flip
), and several instance methods (Push
, Pop
, and 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
Methods can be overloaded, which means that multiple methods may have the same name so long as they have unique signatures. The signature of a method consists of the number and types of its parameters. The signature of a method specifically does not include the return type or parameter modifiers such as Optional, ByRef or ParamArray. The following example shows a class with a number of overloads:
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
The output of the program is:
F()
F(Integer)
F(Object)
F(Integer, Integer)
F(Integer())
G(String)
G(String, Optional String)
Overloads that differ only in optional parameters can be used for "versioning" of libraries. For instance, v1 of a library might include a function with optional parameters:
Sub fopen(fileName As String, Optional accessMode as Integer = 0)
Then v2 of the library wants to add another optional parameter "password", and it wants to do so without breaking source compatibility (so applications that used to target v1 can be recompiled), and without breaking binary compatibility (so applications that used to reference v1 can now reference v2 without recompilation). This is how v2 will look:
Sub fopen(file As String, mode as Integer)
Sub fopen(file As String, Optional mode as Integer = 0, Optional pword As String = "")
Note that optional parameters in a public API are not CLS-compliant. However, they can be consumed at least by Visual Basic and C#4 and F#.
There are two types of methods: subroutines, which do not return values, and functions, which do. The body and End
construct of a method may only be omitted if the method is defined in an interface or has the MustOverride
modifier. If no return type is specified on a function and strict semantics are being used, a compile-time error occurs; otherwise the type is implicitly Object
or the type of the method's type character. The accessibility domain of the return type and parameter types of a method must be the same as or a superset of the accessibility domain of the method itself.
A regular method is one with neither Async
nor Iterator
modifiers. It may be a subroutine or a function. Section Regular Methods details what happens when a regular method is invoked.
An iterator method is one with the Iterator
modifier and no Async
modifier. It must be a function, and its return type must be IEnumerator
, IEnumerable
, or IEnumerator(Of T)
or IEnumerable(Of T)
for some T
, and it must have no ByRef
parameters. Section Iterator Methods details what happens when an iterator method is invoked.
An async method is one with the Async
modifier and no Iterator
modifier. It must be either a subroutine, or a function with return type Task
or Task(Of T)
for some T
, and must have no ByRef
parameters. Section Async Methods details what happens when an async method is invoked.
It is a compile-time error if a method is not one of these three kinds of method.
Subroutine and function declarations are special in that their beginning and end statements must each start at the beginning of a logical line. Additionally, the body of a non-MustOverride
subroutine or function declaration must start at the beginning of a logical line. For example:
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
An external method declaration introduces a new method whose implementation is provided external to the program.
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
;
Because an external method declaration provides no actual implementation, it has no method body or End
construct. External methods are implicitly shared, may not have type parameters, and may not handle events or implement interface members. If no return type is specified on a function and strict semantics are being used, a compile-time error occurs. Otherwise the type is implicitly Object
or the type of the method's type character. The accessibility domain of the return type and parameter types of an external method must be the same as or a superset of the accessibility domain of the external method itself.
The library clause of an external method declaration specifies the name of the external file that implements the method. The optional alias clause is a string that specifies the numeric ordinal (prefixed by a #
character) or name of the method in the external file. A single-character set modifier may also be specified, which governs the character set used to marshal strings during a call to the external method. The Unicode
modifier marshals all strings to Unicode values, the Ansi
modifier marshals all strings to ANSI values, and the Auto
modifier marshals the strings according to .NET Framework rules based on the name of the method, or the alias name if specified. If no modifier is specified, the default is Ansi
.
If Ansi
or Unicode
is specified, then the method name is looked up in the external file with no modification. If Auto
is specified, then method name lookup depends on the platform. If the platform is considered to be ANSI (for example, Windows 95, Windows 98, Windows ME), then the method name is looked up with no modification. If the lookup fails, an A
is appended and the lookup tried again. If the platform is considered to be Unicode (for example, Windows NT, Windows 2000, Windows XP), then a W
is appended and the name is looked up. If the lookup fails, the lookup is tried again without the W
. For example:
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
Data types being passed to external methods are marshaled according to the .NET Framework data marshalling conventions with one exception. String variables that are passed by value (that is, ByVal x As String
) are marshaled to the OLE Automation BSTR type, and changes made to the BSTR in the external method are reflected back in the string argument. This is because the type String
in external methods is mutable, and this special marshalling mimics that behavior. String parameters that are passed by reference (i.e. ByRef x As String
) are marshaled as a pointer to the OLE Automation BSTR type. It is possible to override these special behaviors by specifying the System.Runtime.InteropServices.MarshalAsAttribute
attribute on the parameter.
The example demonstrates use of external methods:
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
The Overridable
modifier indicates that a method is overridable. The Overrides
modifier indicates that a method overrides a base-type overridable method that has the same signature. The NotOverridable
modifier indicates that an overridable method cannot be further overridden. The MustOverride
modifier indicates that a method must be overridden in derived classes.
Certain combinations of these modifiers are not valid:
-
Overridable
andNotOverridable
are mutually exclusive and cannot be combined. -
MustOverride
impliesOverridable
(and so cannot specify it) and cannot be combined withNotOverridable
. -
NotOverridable
cannot be combined withOverridable
orMustOverride
and must be combined withOverrides
. -
Overrides
impliesOverridable
(and so cannot specify it) and cannot be combined withMustOverride
.
There are also additional restrictions on overridable methods:
-
A
MustOverride
method may not include a method body or anEnd
construct, may not override another method, and may only appear inMustInherit
classes. -
If a method specifies
Overrides
and there is no matching base method to override, a compile-time error occurs. An overriding method may not specifyShadows
. -
A method may not override another method if the overriding method's accessibility domain is not equal to the accessibility domain of the method being overridden. The one exception is that a method overriding a
Protected Friend
method in another assembly that does not haveFriend
access must specifyProtected
(notProtected Friend
). -
Private
methods may not beOverridable
,NotOverridable
, orMustOverride
, nor may they override other methods. -
Methods in
NotInheritable
classes may not be declaredOverridable
orMustOverride
.
The following example illustrates the differences between overridable and nonoverridable methods:
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
In the example, class Base
introduces a method F
and an Overridable
method G
. The class Derived
introduces a new method F
, thus shadowing the inherited F
, and also overrides the inherited method G
. The example produces the following output:
Base.F
Derived.F
Derived.G
Derived.G
Notice that the statement b.G()
invokes Derived.G
, not Base.G
. This is because the run-time type of the instance (which is Derived
) rather than the compile-time type of the instance (which is Base
) determines the actual method implementation to invoke.
The Shared
modifier indicates a method is a shared method. A shared method does not operate on a specific instance of a type and may be invoked directly from a type rather than through a particular instance of a type. It is valid, however, to use an instance to qualify a shared method. It is invalid to refer to Me
, MyClass
, or MyBase
in a shared method. Shared methods may not be Overridable
, NotOverridable
, or MustOverride
, and they may not override methods. Methods defined in standard modules and interfaces may not specify Shared
, because they are implicitly Shared
already.
A method declared in a structure or class without a Shared
modifier is an instance method. An instance method operates on a given instance of a type. Instance methods can only be invoked through an instance of a type and may refer to the instance through the Me
expression.
The following example illustrates the rules for accessing shared and instance members:
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
Method F
shows that in an instance function member, an identifier can be used to access both instance members and shared members. Method G
shows that in a shared function member, it is an error to access an instance member through an identifier. Method Main
shows that in a member access expression, instance members must be accessed through instances, but shared members can be accessed through types or instances.
A parameter is a variable that can be used to pass information into and out of a method. Parameters of a method are declared by the method's parameter list, which consists of one or more parameters separated by commas.
ParameterList
: Parameter ( Comma Parameter )*
;
Parameter
: Attributes? ParameterModifier* ParameterIdentifier ( 'As' TypeName )?
( Equals ConstantExpression )?
;
ParameterModifier
: 'ByVal' | 'ByRef' | 'Optional' | 'ParamArray'
;
ParameterIdentifier
: Identifier IdentifierModifiers
;
If no type is specified for a parameter and strict semantics are used, a compile-time error occurs. Otherwise the default type is Object
or the type of the parameter's type character. Even under permissive semantics, if one parameter includes an As
clause, all parameters must specify types.
Parameters are specified as value, reference, optional, or paramarray parameters by the modifiers ByVal
, ByRef
, Optional
, and ParamArray
, respectively. A parameter that does not specify ByRef
or ByVal
defaults to ByVal
.
Parameter names are scoped to the entire body of the method and are always publicly accessible. A method invocation creates a copy, specific to that invocation, of the parameters, and the argument list of the invocation assigns values or variable references to the newly created parameters. Because external method declarations and delegate declarations have no body, duplicate parameter names are allowed in parameter lists, but discouraged.
The identifier may be followed by the nullable name modifier ?
to indicate that it is nullable, and also by array name modifiers to indicate that it is an array. They may be combined, e.g. "ByVal x?() As Integer
". It is not allowed to use explicit array bounds; also, if the nullable name modifier is present then an As
clause must be present.
A value parameter is declared with an explicit ByVal
modifier. If the ByVal
modifier is used, the ByRef
modifier may not be specified. A value parameter comes into existence with the invocation of the member the parameter belongs to, and is initialized with the value of the argument given in the invocation. A value parameter ceases to exist upon return of the member.
A method is permitted to assign new values to a value parameter. Such assignments only affect the local storage location represented by the value parameter; they have no effect on the actual argument given in the method invocation.
A value parameter is used when the value of an argument is passed into a method, and modifications of the parameter do not impact the original argument. A value parameter refers to its own variable, one that is distinct from the variable of the corresponding argument. This variable is initialized by copying the value of the corresponding argument. The following example shows a method F
that has a value parameter named 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
The example produces the following output, even though the value parameter p
is modified:
pre: a = 1
p = 1
post: a = 1
A reference parameter is a parameter declared with a ByRef
modifier. If the ByRef
modifier is specified, the ByVal
modifier may not be used. A reference parameter does not create a new storage location. Instead, a reference parameter represents the variable given as the argument in the method or constructor invocation. Conceptually, the value of a reference parameter is always the same as the underlying variable.
Reference parameters act in two modes, either as aliases or through copy-in copy-back.
Aliases. A reference parameter is used when the parameter acts as an alias for a caller-provided argument. A reference parameter does not itself define a variable, but rather refers to the variable of the corresponding argument. Modifications of a reference parameter directly and immediately impact the corresponding argument. The following example shows a method Swap
that has two reference parameters:
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
The output of the program is:
pre: x = 1, y = 2
post: x = 2, y = 1
For the invocation of method Swap
in class Main
, a
represents x,
and b
represents y
. Thus, the invocation has the effect of swapping the values of x
and y
.
In a method that takes reference parameters, it is possible for multiple names to represent the same storage location:
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
In the example the invocation of method F
in G
passes a reference to s
for both a
and b
. Thus, for that invocation, the names s
, a
, and b
all refer to the same storage location, and the three assignments all modify the instance variable s
.
Copy-in copy-back. If the type of the variable being passed to a reference parameter is not compatible with the reference parameter's type, or if a non-variable (e.g. a property) is passed as an argument to a reference parameter, or if the invocation is late-bound, then a temporary variable is allocated and passed to the reference parameter. The value being passed in will be copied into this temporary variable before the method is invoked and will be copied back to the original variable (if there is one and if it's writable) when the method returns. Thus, a reference parameter may not necessarily contain a reference to the exact storage of the variable being passed in, and any changes to the reference parameter may not be reflected in the variable until the method exits. For example:
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
In the case of the first invocation of F
, a temporary variable is created and the value of the property G
is assigned to it and passed into F
. Upon return from F
, the value in the temporary variable is assigned back to the property of G
. In the second case, another temporary variable is created and the value of d
is assigned to it and passed into F
. When returning from F
, the value in the temporary variable is cast back to the type of the variable, Derived
, and assigned to d
. Since the value being passed back cannot be cast to Derived
, an exception is thrown at run time.
An optional parameter is declared with the Optional
modifier. Parameters that follow an optional parameter in the formal parameter list must be optional as well; failure to specify the Optional
modifier on the following parameters will trigger a compile-time error. An optional parameter of some type nullable type T?
or non-nullable type T
must specify a constant expression e
to be used as a default value if no argument is specified. If e
evaluates to Nothing
of type Object, then the default value of the parameter type will be used as the default for the parameter. Otherwise, CType(e, T)
must be a constant expression and it is taken as the default for the parameter.
Optional parameters are the only situation in which an initializer on a parameter is valid. The initialization is always done as a part of the invocation expression, not within the method body itself.
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
The output of the program is:
x = 10, y = 20
x = 30, y = 40
Optional parameters may not be specified in delegate or event declarations, nor in lambda expressions.
ParamArray
parameters are declared with the ParamArray
modifier. If the ParamArray
modifier is present, the ByVal
modifier must be specified, and no other parameter may use the ParamArray
modifier. The ParamArray
parameter's type must be a one-dimensional array, and it must be the last parameter in the parameter list.
A ParamArray
parameter represents an indeterminate number of parameters of the type of the ParamArray
. Within the method itself, a ParamArray
parameter is treated as its declared type and has no special semantics. A ParamArray
parameter is implicitly optional, with a default value of an empty one-dimensional array of the type of the ParamArray
.
A ParamArray
permits arguments to be specified in one of two ways in a method invocation:
-
The argument given for a
ParamArray
can be a single expression of a type that widens to theParamArray
type. In this case, theParamArray
acts precisely like a value parameter. -
Alternatively, the invocation can specify zero or more arguments for the
ParamArray
, where each argument is an expression of a type that is implicitly convertible to the element type of theParamArray
. In this case, the invocation creates an instance of theParamArray
type with a length corresponding to the number of arguments, initializes the elements of the array instance with the given argument values, and uses the newly created array instance as the actual argument.
Except for allowing a variable number of arguments in an invocation, a ParamArray
is precisely equivalent to a value parameter of the same type, as the following example illustrates.
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
The example produces the output
Array contains 3 elements: 1 2 3
Array contains 4 elements: 10 20 30 40
Array contains 0 elements:
The first invocation of F
simply passes the array a
as a value parameter. The second invocation of F
automatically creates a four-element array with the given element values and passes that array instance as a value parameter. Likewise, the third invocation of F
creates a zero-element array and passes that instance as a value parameter. The second and third invocations are precisely equivalent to writing:
F(New Integer() {10, 20, 30, 40})
F(New Integer() {})
ParamArray
parameters may not be specified in delegate or event declarations.
Methods can declaratively handle events raised by objects in instance or shared variables. To handle events, a method declaration specifies the Handles
keyword and lists one or more events.
HandlesClause
: ( 'Handles' EventHandlesList )?
;
EventHandlesList
: EventMemberSpecifier ( Comma EventMemberSpecifier )*
;
EventMemberSpecifier
: Identifier Period IdentifierOrKeyword
| 'MyBase' Period IdentifierOrKeyword
| 'MyClass' Period IdentifierOrKeyword
| 'Me' Period IdentifierOrKeyword
;
An event in the Handles
list is specified by two identifiers separated by a period:
-
The first identifier must be an instance or shared variable in the containing type that specifies the
WithEvents
modifier or theMyBase
orMyClass
orMe
keyword; otherwise, a compile-time error occurs. This variable contains the object that will raise the events handled by this method. -
The second identifier must specify a member of the type of the first identifier. The member must be an event, and may be shared. If a shared variable is specified for the first identifier, then the event must be shared, or an error results.
A handler method M
is considered a valid event handler for an event E
if the statement AddHandler E, AddressOf M
would also be valid. Unlike an AddHandler
statement, however, explicit event handlers allow handling an event with a method with no arguments regardless of whether strict semantics are being used or not:
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
A single member can handle multiple matching events, and multiple methods may handle a single event. A method's accessibility has no effect on its ability to handle events. The following example shows how a method can handle events:
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
This will print out:
Raised
Raised
A type inherits all event handlers provided by its base type. A derived type cannot in any way alter the event mappings it inherits from its base types, but may add additional handlers to the event.
Methods can be added to types from outside of the type declaration using extension methods. Extension methods are methods with the System.Runtime.CompilerServices.ExtensionAttribute
attribute applied to them. They can only be declared in standard modules and must have at least one parameter, which specifies the type the method extends. For example, the following extension method extends the type String
:
Imports System.Runtime.CompilerServices
Module StringExtensions
<Extension> _
Sub Print(s As String)
Console.WriteLine(s)
End Sub
End Module
Note. Although Visual Basic requires extension methods to be declared in a standard module, other languages such as C# may allow them to be declared in other kinds of types. As long as the methods follow the other conventions outlined here and the containing type is not an open generic type and cannot be instantiated, Visual Basic will recognize the extension methods.
When an extension method is invoked, the instance it is being invoked on is passed to the first parameter. The first parameter cannot be declared Optional
or ParamArray
. Any type, including a type parameter, can appear as the first parameter of an extension method. For example, the following methods extend the types Integer()
, any type that implements System.Collections.Generic.IEnumerable(Of T)
, and any type at all:
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
As the previous example shows, interfaces can be extended. Interface extension methods supply the implementation of the method, so types that implement an interface that has extension methods defined on it still only implement the members originally declared by the interface. For example:
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
Extension methods can also have type constraints on their type parameters and, just as with non-extension generic methods, type argument can be inferred:
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
Extension methods can also be accessed through implicit instance expressions within the type being extended:
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
For the purposes of accessibility, extension methods are also treated as members of the standard module they are declared in -- they have no extra access to the members of the type they are extending beyond the access they have by virtue of their declaration context.
Extensions methods are only available when the standard module method is in scope. Otherwise, the original type will not appear to have been extended. For example:
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
Referring to a type when only an extension method on the type is available will still produce a compile-time error.
It is important to note that extension methods are considered to be members of the type in all contexts where members are bound, such as the strongly-typed For Each
pattern. For example:
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
Delegates can also be created that refer to extension methods. Thus, the code:
Delegate Sub D1()
Module Test
Sub Main()
Dim s As String = "Hello, World!"
Dim d As D1
d = AddressOf s.Print
d()
End Sub
End Module
is roughly equivalent to:
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
Note. Visual Basic normally inserts a check on an instance method call that causes a System.NullReferenceException
to occur if the instance the method is being invoked on is Nothing
. In the case of extension methods, there is no efficient way to insert this check, so extension methods will need to explicitly check for Nothing
.
Note. A value type will be boxed when being passed as a ByVal
argument to a parameter typed as an interface. This implies that side effects of the extension method will operate on a copy of the structure instead of the original. While the language puts no restrictions on the first argument of an extension method, it is recommended that extension methods are not used to extend value types or that when extending value types, the first parameter is passed ByRef
to ensure that side effects operate on the original value.
A partial method is a method that specifies a signature but not the body of the method. The body of the method can be supplied by another method declaration with the same name and signature, most likely in another partial declaration of the type. For example:
a.vb:
' Designer generated code
Public Partial Class MyForm
Private Partial Sub ValidateControls()
End Sub
Public Sub New()
' Initialize controls
...
ValidateControls()
End Sub
End Class
b.vb:
Public Partial Class MyForm
Public Sub ValidateControls()
' Validation logic goes here
...
End Sub
End Class
In this example, a partial declaration of the class MyForm
declares a partial method ValidateControls
with no implementation. The constructor in the partial declaration calls the partial method, even though there is no body supplied in the file. The other partial declaration of MyForm
then supplies the implementation of the method.
Partial methods can be called regardless of whether a body has been supplied; if no method body is supplied, the call is ignored. For example:
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
Any expressions that are passed in as arguments to a partial method call that is ignored are ignored also and not evaluated. (Note. This means that partial methods are a very efficient way of providing behavior that is defined across two partial types, since the partial methods have no cost if they are not used.)
The partial method declaration must be declared as Private
and must always be a subroutine with no statements in its body. Partial methods cannot themselves implement interface methods, although the method that supplies their body can.
Only one method can supply a body to a partial method. A method supplying a body to a partial method must have the same signature as the partial method, the same constraints on any type parameters, the same declaration modifiers, and the same parameter and type parameter names. Attributes on the partial method and the method that supplies its body are merged, as are any attributes on the methods' parameters. Similarly, the list of events that the methods handle is merged. For example:
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
Constructors are special methods that allow control over initialization. They are run after the program begins or when an instance of a type is created. Unlike other members, constructors are not inherited and do not introduce a name into a type's declaration space. Constructors may only be invoked by object-creation expressions or by the .NET Framework; they may never be directly invoked.
Note. Constructors have the same restriction on line placement that subroutines have. The beginning statement, end statement and block must all appear at the beginning of a logical line.
ConstructorMemberDeclaration
: Attributes? ConstructorModifier* 'Sub' 'New'
( OpenParenthesis ParameterList? CloseParenthesis )? LineTerminator
Block?
'End' 'Sub' StatementTerminator
;
ConstructorModifier
: AccessModifier
| 'Shared'
;
Instance constructors initialize instances of a type and are run by the .NET Framework when an instance is created. The parameter list of a constructor is subject to the same rules as the parameter list of a method. Instance constructors may be overloaded.
All constructors in reference types must invoke another constructor. If the invocation is explicit, it must be the first statement in the constructor method body. The statement can either invoke another of the type's instance constructors -- for example, Me.New(...)
or MyClass.New(...)
-- or if it is not a structure it can invoke an instance constructor of the type's base type -- for example, MyBase.New(...)
. It is invalid for a constructor to invoke itself. If a constructor omits a call to another constructor, MyBase.New()
is implicit. If there is no parameterless base type constructor, a compile-time error occurs. Because Me
is not considered to be constructed until after the call to a base class constructor, the parameters to a constructor invocation statement cannot reference Me
, MyClass
, or MyBase
implicitly or explicitly.
When a constructor's first statement is of the form MyBase.New(...)
, the constructor implicitly performs the initializations specified by the variable initializers of the instance variables declared in the type. This corresponds to a sequence of assignments that are executed immediately after invoking the direct base type constructor. Such ordering ensures that all base instance variables are initialized by their variable initializers before any statements that have access to the instance are executed. For example:
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
When New B()
is used to create an instance of B
, the following output is produced:
x = 1, y = 1
The value of y
is 1
because the variable initializer is executed after the base class constructor is invoked. Variable initializers are executed in the textual order they appear in the type declaration.
When a type declares only Private
constructors, it is not possible in general for other types to derive from the type or create instances of the type; the only exception is types nested within the type. Private
constructors are commonly used in types that contain only Shared
members.
If a type contains no instance constructor declarations, a default constructor is automatically provided. The default constructor simply invokes the parameterless constructor of the direct base type. If the direct base type does not have an accessible parameterless constructor, a compile-time error occurs. The declared access type for the default constructor is Public
unless the type is MustInherit
, in which case the default constructor is Protected
.
Note. The default access for a MustInherit
type's default constructor is Protected
because MustInherit
classes cannot be created directly. So there is no point in making the default constructor Public
.
In the following example a default constructor is provided because the class contains no constructor declarations:
Class Message
Dim sender As Object
Dim text As String
End Class
Thus, the example is precisely equivalent to the following:
Class Message
Dim sender As Object
Dim text As String
Sub New()
End Sub
End Class
Default constructors that are emitted into a designer generated class marked with the attribute Microsoft.VisualBasic.CompilerServices.DesignerGeneratedAttribute
will call the method Sub InitializeComponent()
, if it exists, after the call to the base constructor. (Note. This allows designer generated files, such as those created by the WinForms designer, to omit the constructor in the designer file. This enables the programmer to specify it themselves, if they so choose.)
Shared constructors initialize a type's shared variables; they are run after the program begins executing, but before any references to a member of the type. A shared constructor specifies the Shared
modifier, unless it is in a standard module in which case the Shared
modifier is implied.
Unlike instance constructors, shared constructors have implicit public access, have no parameters, and may not call other constructors. Before the first statement in a shared constructor, the shared constructor implicitly performs the initializations specified by the variable initializers of the shared variables declared in the type. This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor. The variable initializers are executed in the textual order they appear in the type declaration.
The following example shows an Employee
class with a shared constructor that initializes a shared variable:
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
A separate shared constructor exists for each closed generic type. Because the shared constructor is executed exactly once for each closed type, it is a convenient place to enforce run-time checks on the type parameter that cannot be checked at compile-time via constraints. For example, the following type uses a shared constructor to enforce that the type parameter is Integer
or 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
Exactly when shared constructors are run is mostly implementation dependent, though several guarantees are provided if a shared constructor is explicitly defined:
-
Shared constructors are run before the first access to any static field of the type.
-
Shared constructors are run before the first invocation of any static method of the type.
-
Shared constructors are run before the first invocation of any constructor for the type.
The above guarantees do not apply in the situation where a shared constructor is implicitly created for shared initializers. The output from the following example is uncertain, because the exact ordering of loading and therefore of shared constructor execution is not defined:
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
The output could be either of the following:
Init A
A.F
Init B
B.F
or
Init B
Init A
A.F
B.F
By contrast, the following example produces predictable output. Note that the Shared
constructor for the class A
never executes, even though class B
derives from it:
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
The output is:
Init B
B.G
It is also possible to construct circular dependencies that allow Shared
variables with variable initializers to be observed in their default value state, as in the following example:
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
This produces the output:
X = 1, Y = 2
To execute the Main
method, the system first loads class B
. The Shared
constructor of class B
proceeds to compute the initial value of Y
, which recursively causes class A
to be loaded because the value of A.X
is referenced. The Shared
constructor of class A
in turn proceeds to compute the initial value of X
, and in doing so fetches the default value of Y
, which is zero. A.X
is thus initialized to 1
. The process of loading A
then completes, returning to the calculation of the initial value of Y
, the result of which becomes 2
.
Had the Main
method instead been located in class A
, the example would have produced the following output:
X = 2, Y = 1
Avoid circular references in Shared
variable initializers since it is generally impossible to determine the order in which classes containing such references are loaded.
Events are used to notify code of a particular occurrence. An event declaration consists of an identifier, either a delegate type or a parameter list, and an optional Implements
clause.
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'
;
If a delegate type is specified, the delegate type may not have a return type. If a parameter list is specified, it may not contain Optional
or ParamArray
parameters. The accessibility domain of the parameter types and/or delegate type must be the same as, or a superset of, the accessibility domain of the event itself. Events may be shared by specifying the Shared
modifier.
In addition to the member name added to the type's declaration space, an event declaration implicitly declares several other members. Given an event named X
, the following members are added to the declaration space:
-
If the form of the declaration is a method declaration, a nested delegate class named
XEventHandler
is introduced. The nested delegate class matches the method declaration and has the same accessibility as the event. The attributes in the parameter list apply to the parameters of the delegate class. -
A
Private
instance variable typed as the delegate, namedXEvent
. -
Two methods named
add_X
andremove_X
which cannot be invoked, overridden or overloaded.
If a type attempts to declare a name that matches one of the above names, a compile-time error will result, and the implicit add_X
and remove_X
declarations are ignored for the purposes of name binding. It is not possible to override or overload any of the introduced members, although it is possible to shadow them in derived types. For example, the class declaration
Class Raiser
Public Event Constructed(i As Integer)
End Class
is equivalent to the following declaration
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
Declaring an event without specifying a delegate type is the simplest and most compact syntax, but has the disadvantage of declaring a new delegate type for each event. For example, in the following example, three hidden delegate types are created, even though all three events have the same parameter list:
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
In the following example, the events simply use the same delegate, 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
Events can be handled in one of two ways: statically or dynamically. Statically handling events is simpler and only requires a WithEvents
variable and a Handles
clause. In the following example, class Form1
statically handles the event Click
of object 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
Dynamically handling events is more complex because the event must be explicitly connected and disconnected to in code. The statement AddHandler
adds a handler for an event, and the statement RemoveHandler
removes a handler for an event. The next example shows a class Form1
that adds Button1_Click
as an event handler for Button1
's Click
event:
Public Class Form1
Public Sub New()
' Add Button1_Click as an event handler for Button1's Click event.
AddHandler Button1.Click, AddressOf Button1_Click
End Sub
Private Button1 As Button = New Button()
Sub Button1_Click(sender As Object, e As EventArgs)
Console.WriteLine("Button1 was clicked!")
End Sub
Public Sub Disconnect()
RemoveHandler Button1.Click, AddressOf Button1_Click
End Sub
End Class
In method Disconnect
, the event handler is removed.
As discussed in the previous section, event declarations implicitly define a field, an add_
method, and a remove_
method that are used to keep track of event handlers. In some situations, however, it may be desirable to provide custom code for tracking event handlers. For example, if a class defines forty events of which only a few will ever be handled, using a hash table instead of forty fields to track the handlers for each event may be more efficient. Custom events allow the add_X
and remove_X
methods to be defined explicitly, which enables custom storage for event handlers.
Custom events are declared in the same way that events that specify a delegate type are declared, with the exception that the keyword Custom
must precede the Event
keyword. A custom event declaration contains three declarations: an AddHandler
declaration, a RemoveHandler
declaration and a RaiseEvent
declaration. None of the declarations can have any modifiers, although they can have attributes.
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
;
For example:
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
The AddHandler
and RemoveHandler
declaration take one ByVal
parameter, which must be of the delegate type of the event. When an AddHandler
or RemoveHandler
statement is executed (or a Handles
clause automatically handles an event), the corresponding declaration will be called. The RaiseEvent
declaration takes the same parameters as the event delegate and will be called when a RaiseEvent
statement is executed. All of the declarations must be provided and are considered to be subroutines.
Note that AddHandler
, RemoveHandler
and RaiseEvent
declarations have the same restriction on line placement that subroutines have. The beginning statement, end statement and block must all appear at the beginning of a logical line.
In addition to the member name added to the type's declaration space, a custom event declaration implicitly declares several other members. Given an event named X
, the following members are added to the declaration space:
-
A method named
add_X
, corresponding to theAddHandler
declaration. -
A method named
remove_X
, corresponding to theRemoveHandler
declaration. -
A method named
fire_X
, corresponding to theRaiseEvent
declaration.
If a type attempts to declare a name that matches one of the above names, a compile-time error will result, and the implicit declarations are all ignored for the purposes of name binding. It is not possible to override or overload any of the introduced members, although it is possible to shadow them in derived types.
Note. Custom
is not a reserved word.
As of Microsoft Visual Basic 11.0, events declared in a file compiled with /target:winmdobj
, or declared in an interface in such a file and then implemented elsewhere, are treated a little differently.
-
External tools used to build the winmd will typically allow only certain delegate types such as
System.EventHandler(Of T)
orSystem.TypedEventHandle(Of T, U)
, and will disallow others. -
The
XEvent
field has typeSystem.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenTable(Of T)
whereT
is the delegate type. -
The AddHandler accessor returns a
System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken
, and the RemoveHandler accessor takes a single parameter of the same type.
Here is an example of such a custom event.
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
A constant is a constant value that is a member of a type.
ConstantMemberDeclaration
: Attributes? ConstantModifier* 'Const' ConstantDeclarators StatementTerminator
;
ConstantModifier
: AccessModifier
| 'Shadows'
;
ConstantDeclarators
: ConstantDeclarator ( Comma ConstantDeclarator )*
;
ConstantDeclarator
: Identifier ( 'As' TypeName )? Equals ConstantExpression StatementTerminator
;
Constants are implicitly shared. If the declaration contains an As
clause, the clause specifies the type of the member introduced by the declaration. If the type is omitted then the type of the constant is inferred. The type of a constant may only be a primitive type or Object
. If a constant is typed as Object
and there is no type character, the real type of the constant will be the type of the constant expression. Otherwise, the type of the constant is the type of the constant's type character.
The following example shows a class named Constants
that has two public constants:
Class Constants
Public Const A As Integer = 1
Public Const B As Integer = A + 1
End Class
Constants can be accessed through the class, as in the following example, which prints out the values of Constants.A
and Constants.B
.
Module Test
Sub Main()
Console.WriteLine(Constants.A & ", " & Constants.B)
End Sub
End Module
A constant declaration that declares multiple constants is equivalent to multiple declarations of single constants. The following example declares three constants in one declaration statement.
Class A
Protected Const x As Integer = 1, y As Long = 2, z As Short = 3
End Class
This declaration is equivalent to the following:
Class A
Protected Const x As Integer = 1
Protected Const y As Long = 2
Protected Const z As Short = 3
End Class
The accessibility domain of the type of the constant must be the same as or a superset of the accessibility domain of the constant itself. The constant expression must yield a value of the constant's type or of a type that is implicitly convertible to the constant's type. The constant expression may not be circular; that is, a constant may not be defined in terms of itself.
The compiler automatically evaluates the constant declarations in the appropriate order. In the following example, the compiler first evaluates Y
, then Z
, and finally X
, producing the values 10, 11, and 12, respectively.
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
When a symbolic name for a constant value is desired, but the type of the value is not permitted in a constant declaration or when the value cannot be computed at compile time by a constant expression, a read-only variable may be used instead.
An instance or shared variable is a member of a type that can store information.
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
;
The Dim
modifier must be specified if no modifiers are specified, but may be omitted otherwise. A single variable declaration may include multiple variable declarators; each variable declarator introduces a new instance or shared member.
If an initializer is specified, only one instance or shared variable may be declared by the variable declarator:
Class Test
Dim a, b, c, d As Integer = 10 ' Invalid: multiple initialization
End Class
This restriction does not apply to object initializers:
Class Test
Dim a, b, c, d As New Collection() ' OK
End Class
A variable declared with the Shared
modifier is a shared variable. A shared variable identifies exactly one storage location regardless of the number of instances of the type that are created. A shared variable comes into existence when a program begins executing, and ceases to exist when the program terminates.
A shared variable is shared only among instances of a particular closed generic type. For example, the 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
Prints out:
1
1
2
A variable declared without the Shared
modifier is called an instance variable. Every instance of a class contains a separate copy of all instance variables of the class. An instance variable of a reference type comes into existence when a new instance of that type is created, and ceases to exist when there are no references to that instance and the Finalize
method has executed. An instance variable of a value type has exactly the same lifetime as the variable to which it belongs. In other words, when a variable of a value type comes into existence or ceases to exist, so does the instance variable of the value type.
If the declarator contains an As
clause, the clause specifies the type of the members introduced by the declaration. If the type is omitted and strict semantics are being used, a compile-time error occurs. Otherwise the type of the members is implicitly Object
or the type of the members' type character.
Note. There is no ambiguity in the syntax: if a declarator omits a type, it will always use the type of a following declarator.
The accessibility domain of an instance or shared variable's type or array element type must be the same as or a superset of the accessibility domain of the instance or shared variable itself.
The following example shows a Color
class that has internal instance variables named redPart
, greenPart
, and 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
When an instance or shared variable declaration includes a ReadOnly
modifier, assignments to the variables introduced by the declaration may only occur as part of the declaration or in a constructor in the same class. Specifically, assignments to a read-only instance or shared variable are permitted only in the following situations:
-
In the variable declaration that introduces the instance or shared variable (by including a variable initializer in the declaration).
-
For an instance variable, in the instance constructors of the class that contains the variable declaration. The instance variable can only be accessed in an unqualified manner or through
Me
orMyClass
. -
For a shared variable, in the shared constructor of the class that contains the shared variable declaration.
A shared read-only variable is useful when a symbolic name for a constant value is desired, but when the type of the value is not permitted in a constant declaration, or when the value cannot be computed at compile time by a constant expression.
An example of the first such application follows, in which color shared variables are declared ReadOnly
to prevent them from being changed by other programs:
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
Constants and read-only shared variables have different semantics. When an expression references a constant, the value of the constant is obtained at compile time, but when an expression references a read-only shared variable, the value of the shared variable is not obtained until run time. Consider the following application, which consists of two separate programs.
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
The namespaces Program1
and Program2
denote two programs that are compiled separately. Because variable Program1.Utils.X
is declared as Shared ReadOnly
, the value output by the Console.WriteLine
statement is not known at compile time, but rather is obtained at run time. Thus, if the value of X
is changed and Program1
is recompiled, the Console.WriteLine
statement will output the new value even if Program2
is not recompiled. However, if X
had been a constant, the value of X
would have been obtained at the time Program2
was compiled, and would have remained unaffected by changes in Program1
until Program2
was recompiled.
A type can declare that it handles some set of events raised by one of its instance or shared variables by declaring the instance or shared variable that raises the events with the WithEvents
modifier. For example:
Class Raiser
Public Event E1()
Public Sub Raise()
RaiseEvent E1
End Sub
End Class
Module Test
Private WithEvents x As Raiser
Private Sub E1Handler() Handles x.E1
Console.WriteLine("Raised")
End Sub
Public Sub Main()
x = New Raiser()
End Sub
End Module
In this example, the method E1Handler
handles the event E1
that is raised by the instance of the type Raiser
stored in the instance variable x
.
The WithEvents
modifier causes the variable to be renamed with a leading underscore and replaced with a property of the same name that does the event hookup. For example, if the variable's name is F
, it is renamed to _F
and a property F
is implicitly declared. If there is a collision between the variable's new name and another declaration, a compile-time error will be reported. Any attributes applied to the variable are carried over to the renamed variable.
The implicit property created by a WithEvents
declaration takes care of hooking and unhooking the relevant event handlers. When a value is assigned to the variable, the property first calls the remove
method for the event on the instance currently in the variable (unhooking the existing event handler, if any). Next the assignment is made, and the property calls the add
method for the event on the new instance in the variable (hooking up the new event handler). The following code is equivalent to the code above for the standard module Test
:
Module Test
Private _x As Raiser
Public Property x() As Raiser
Get
Return _x
End Get
Set (Value As Raiser)
' Unhook any existing handlers.
If _x IsNot Nothing Then
RemoveHandler _x.E1, AddressOf E1Handler
End If
' Change value.
_x = Value
' Hook-up new handlers.
If _x IsNot Nothing Then
AddHandler _x.E1, AddressOf E1Handler
End If
End Set
End Property
Sub E1Handler()
Console.WriteLine("Raised")
End Sub
Sub Main()
x = New Raiser()
End Sub
End Module
It is not valid to declare an instance or shared variable as WithEvents
if the variable is typed as a structure. In addition, WithEvents
may not be specified in a structure, and WithEvents
and ReadOnly
cannot be combined.
Instance and shared variable declarations in classes and instance variable declarations (but not shared variable declarations) in structures may include variable initializers. For Shared
variables, variable initializers correspond to assignment statements that are executed after the program begins, but before the Shared
variable is first referenced. For instance variables, variable initializers correspond to assignment statements that are executed when an instance of the class is created. Structures cannot have instance variable initializers because their parameterless constructors cannot be modified.
Consider the following example:
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
The example produces the following output:
x = 1.4142135623731, i = 100, s = Hello
An assignment to x
occurs when the class is loaded, and assignments to i
and s
occur when a new instance of the class is created.
It is useful to think of variable initializers as assignment statements that are automatically inserted in the block of the type's constructor. The following example contains several instance variable initializers.
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
The example corresponds to the code shown below, where each comment indicates an automatically inserted statement.
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
All variables are initialized to the default value of their type before any variable initializers are executed. For example:
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
Because b
is automatically initialized to its default value when the class is loaded and i
is automatically initialized to its default value when an instance of the class is created, the preceding code produces the following output:
b = False, i = 0
Each variable initializer must yield a value of the variable's type or of a type that is implicitly convertible to the variable's type. A variable initializer may be circular or refer to a variable that will be initialized after it, in which case the value of the referenced variable is its default value for the purposes of the initializer. Such an initializer is of dubious value.
There are three forms of variable initializers: regular initializers, array-size initializers, and object initializers. The first two forms appear after an equal sign that follows the type name, the latter two are part of the declaration itself. Only one form of initializer may be used on any particular declaration.
A regular initializer is an expression that is implicitly convertible to the type of the variable. It appears after an equal sign that follows the type name and must be classified as a value. For example:
Module Test
Dim x As Integer = 10
Dim y As Integer = 20
Sub Main()
Console.WriteLine("x = " & x & ", y = " & y)
End Sub
End Module
This program produces the output:
x = 10, y = 20
If a variable declaration has a regular initializer, then only a single variable can be declared at a time. For example:
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
An object initializer is specified using an object creation expression in the place of the type name. An object initializer is equivalent to a regular initializer assigning the result of the object creation expression to the variable. So
Module TestModule
Sub Main()
Dim x As New Test(10)
End Sub
End Module
is equivalent to
Module TestModule
Sub Main()
Dim x As Test = New Test(10)
End Sub
End Module
The parenthesis in an object initializer is always interpreted as the argument list for the constructor and never as array type modifiers. A variable name with an object initializer cannot have an array type modifier or a nullable type modifier.
An array-size initializer is a modifier on the name of the variable that gives a set of dimension upper bounds denoted by expressions.
ArraySizeInitializationModifier
: OpenParenthesis BoundList CloseParenthesis ArrayTypeModifiers?
;
BoundList
: Bound ( Comma Bound )*
;
Bound
: Expression
| '0' 'To' Expression
;
The upper bound expressions must be classified as values and must be implicitly convertible to Integer
. The set of upper bounds is equivalent to a variable initializer of an array-creation expression with the given upper bounds. The number of dimensions of the array type is inferred from the array size initializer. So
Module Test
Sub Main()
Dim x(5, 10) As Integer
End Sub
End Module
is equivalent to
Module Test
Sub Main()
Dim x As Integer(,) = New Integer(5, 10) {}
End Sub
End Module
All upper bounds must be equal to or greater than -1, and all dimensions must have an upper bound specified. If the element type of the array being initialized is itself an array type, the array-type modifiers go to the right of the array-size initializer. For example
Module Test
Sub Main()
Dim x(5,10)(,,) As Integer
End Sub
End Module
declares a local variable x
whose type is a two-dimensional array of three-dimensional arrays of Integer
, initialized to an array with bounds of 0..5
in the first dimension and 0..10
in the second dimension. It is not possible to use an array size initializer to initialize the elements of a variable whose type is an array of arrays.
A variable declaration with an array-size initializer cannot have an array type modifier on its type or a regular initializer.
Classes that derive from the class System.MarshalByRefObject
are marshaled across context boundaries using proxies (that is, by reference) rather than through copying (that is, by value). This means that an instance of such a class may not be a true instance but instead may just be a stub that marshals variable accesses and method calls across a context boundary.
As a result, it is not possible to create a reference to the storage location of variables defined on such classes. This means that variables typed as classes derived from System.MarshalByRefObject
cannot be passed to reference parameters, and methods and variables of variables typed as value types may not be accessed. Instead, Visual Basic treats variables defined on such classes as if they were properties (since the restrictions are the same on properties).
There is one exception to this rule: a member implicitly or explicitly qualified with Me
is exempt from the above restrictions, because Me
is always guaranteed to be an actual object, not a proxy.
Properties are a natural extension of variables; both are named members with associated types, and the syntax for accessing variables and properties is the same. Unlike variables, however, properties do not denote storage locations. Instead, properties have accessors, which specify the statements to execute in order to read or write their values.
Properties are defined with property declarations. The first part of a property declaration resembles a field declaration. The second part includes a Get
accessor and/or a Set
accessor.
PropertyMemberDeclaration
: RegularPropertyMemberDeclaration
| MustOverridePropertyMemberDeclaration
| AutoPropertyMemberDeclaration
;
PropertySignature
: 'Property'
Identifier ( OpenParenthesis ParameterList? CloseParenthesis )?
( 'As' Attributes? TypeName )?
;
RegularPropertyMemberDeclaration
: Attributes? PropertyModifier* PropertySignature
ImplementsClause? LineTerminator
PropertyAccessorDeclaration+
'End' 'Property' StatementTerminator
;
MustOverridePropertyMemberDeclaration
: Attributes? MustOverridePropertyModifier+ PropertySignature
ImplementsClause? StatementTerminator
;
AutoPropertyMemberDeclaration
: Attributes? AutoPropertyModifier* 'Property' Identifier
( OpenParenthesis ParameterList? CloseParenthesis )?
( 'As' Attributes? TypeName )? ( Equals Expression )?
ImplementsClause? LineTerminator
| Attributes? AutoPropertyModifier* 'Property' Identifier
( OpenParenthesis ParameterList? CloseParenthesis )?
'As' Attributes? 'New'
( NonArrayTypeName ( OpenParenthesis ArgumentList? CloseParenthesis )? )?
ObjectCreationExpressionInitializer?
ImplementsClause? LineTerminator
;
InterfacePropertyMemberDeclaration
: Attributes? InterfacePropertyModifier* PropertySignature StatementTerminator
;
AutoPropertyModifier
: AccessModifier
| 'Shadows'
| 'Shared'
| 'Overridable'
| 'NotOverridable'
| 'Overrides'
| 'Overloads'
;
PropertyModifier
: AutoPropertyModifier
| 'Default'
| 'ReadOnly'
| 'WriteOnly'
| 'Iterator'
;
MustOverridePropertyModifier
: PropertyModifier
| 'MustOverride'
;
InterfacePropertyModifier
: 'Shadows'
| 'Overloads'
| 'Default'
| 'ReadOnly'
| 'WriteOnly'
;
PropertyAccessorDeclaration
: PropertyGetDeclaration
| PropertySetDeclaration
;
In the example below, the Button
class defines a Caption
property.
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
Based on the Button
class above, the following is an example of use of the Caption
property:
Dim okButton As Button = New Button()
okButton.Caption = "OK" ' Invokes Set accessor.
Dim s As String = okButton.Caption ' Invokes Get accessor.
Here, the Set
accessor is invoked by assigning a value to the property, and the Get
accessor is invoked by referencing the property in an expression.
If no type is specified for a property and strict semantics are being used, a compile-time error occurs; otherwise the type of the property is implicitly Object
or the type of the property's type character. A property declaration may contain either a Get
accessor, which retrieves the value of the property, a Set
accessor, which stores the value of the property, or both. Because a property implicitly declares methods, a property may be declared with the same modifiers as a method. If the property is defined in an interface or defined with the MustOverride
modifier, the property body and the End
construct must be omitted; otherwise, a compile-time error occurs.
The index parameter list makes up the signature of the property, so properties may be overloaded on index parameters but not on the type of the property. The index parameter list is the same as for a regular method. However, none of the parameters may be modified with the ByRef
modifier and none of them may be named Value
(which is reserved for the implicit value parameter in the Set
accessor).
A property may be declared as follows:
-
If the property specifies no property type modifier, the property must have both a
Get
accessor and aSet
accessor. The property is said to be a read-write property. -
If the property specifies the
ReadOnly
modifier, the property must have aGet
accessor and may not have aSet
accessor. The property is said to be read-only property. It is a compile-time error for a read-only property to be the target of an assignment. -
If the property specifies the
WriteOnly
modifier, the property must have aSet
accessor and may not have aGet
accessor. The property is said to be write-only property. It is a compile-time error to reference a write-only property in an expression except as the target of an assignment or as an argument to a method.
The Get
and Set
accessors of a property are not distinct members, and it is not possible to declare the accessors of a property separately. The following example does not declare a single read-write property. Rather, it declares two properties with the same name, one read-only and one write-only:
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
Since two members declared in the same class cannot have the same name, the example causes a compile-time error.
By default, the accessibility of a property's Get
and Set
accessors is the same as the accessibility of the property itself. However, the Get
and Set
accessors can also specify accessibility separately from the property. In that case, the accessibility of an accessor must be more restrictive than the accessibility of the property and only one accessor can have a different accessibility level from the property. Access types are considered more or less restrictive as follows:
-
Private
is more restrictive thanPublic
,Protected Friend
,Protected
, orFriend
. -
Friend
is more restrictive thanProtected Friend
orPublic
. -
Protected
is more restrictive thanProtected Friend
orPublic
. -
Protected Friend
is more restrictive thanPublic
.
When one of a property's accessors is accessible but the other one is not, the property is treated as if it was read-only or write-only. For example:
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
When a derived type shadows a property, the derived property hides the shadowed property with respect to both reading and writing. In the following example, the P
property in B
hides the P
property in A
with respect to both reading and writing:
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
The accessibility domain of the return type or parameter types must be the same as or a superset of the accessibility domain of the property itself. A property may only have one Set
accessor and one Get
accessor.
Except for differences in declaration and invocation syntax, Overridable
, NotOverridable
, Overrides
, MustOverride
, and MustInherit
properties behave exactly like Overridable
, NotOverridable
, Overrides
, MustOverride
, and MustInherit
methods. When a property is overridden, the overriding property must be of the same type (read-write, read-only, write-only). An Overridable
property cannot contain a Private
accessor.
In the following example X
is an Overridable
read-only property, Y
is an Overridable
read-write property, and Z
is a MustOverride
read-write property.
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
Because Z
is MustOverride
, the containing class A
must be declared MustInherit
.
By contrast, a class that derives from class A
is shown below:
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
Here, the declarations of properties X
,Y
, and Z
override the base properties. Each property declaration exactly matches the accessibility modifiers, type, and name of the corresponding inherited property. The Get
accessor of property X
and the Set
accessor of property Y
use the MyBase
keyword to access the inherited properties. The declaration of property Z
overrides the MustOverride
property -- thus, there are no outstanding MustOverride
members in class B
, and B
is permitted to be a regular class.
Properties can be used to delay initialization of a resource until the moment it is first referenced. For example:
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
The ConsoleStreams
class contains three properties, In
, Out
, and Error
, that represent the standard input, output, and error devices, respectively. By exposing these members as properties, the ConsoleStreams
class can delay their initialization until they are actually used. For example, upon first referencing the Out
property, as in ConsoleStreams.Out.WriteLine("hello, world")
, the underlying TextWriter
for the output device is initialized. But if the application makes no reference to the In
and Error
properties, then no objects are created for those devices.
A Get
accessor (getter) is declared by using a property Get
declaration. A property Get
declaration consists of the keyword Get
followed by a statement block. Given a property named P
, a Get
accessor declaration implicitly declares a method with the name get_P
with the same modifiers, type, and parameter list as the property. If the type contains a declaration with that name, a compile-time error results, but the implicit declaration is ignored for the purposes of name binding.
A special local variable, which is implicitly declared in the Get
accessor body's declaration space with the same name as the property, represents the return value of the property. The local variable has special name resolution semantics when used in expressions. If the local variable is used in a context that expects an expression that is classified as a method group, such as an invocation expression, then the name resolves to the function rather than to the local variable. For example:
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
The use of parentheses can cause ambiguous situations (such as F(1)
where F
is a property whose type is a one-dimensional array). In all ambiguous situations, the name resolves to the property rather than the local variable. For example:
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
When control flow leaves the Get
accessor body, the value of the local variable is passed back to the invocation expression. Because invoking a Get
accessor is conceptually equivalent to reading the value of a variable, it is considered bad programming style for Get
accessors to have observable side effects, as illustrated in the following example:
Class Counter
Private Value As Integer
Public ReadOnly Property NextValue() As Integer
Get
Value += 1
Return Value
End Get
End Property
End Class
The value of the NextValue
property depends on the number of times the property has previously been accessed. Thus, accessing the property produces an observable side effect, and the property should instead be implemented as a method.
The "no side effects" convention for Get
accessors does not mean that Get
accessors should always be written to simply return values stored in variables. Indeed, Get
accessors often compute the value of a property by accessing multiple variables or invoking methods. However, a properly designed Get
accessor performs no actions that cause observable changes in the state of the object.
Note. Get
accessors have the same restriction on line placement that subroutines have. The beginning statement, end statement and block must all appear at the beginning of a logical line.
PropertyGetDeclaration
: Attributes? AccessModifier? 'Get' LineTerminator
Block?
'End' 'Get' StatementTerminator
;
A Set
accessor (setter) is declared by using a property set declaration. A property set declaration consists of the keyword Set
, an optional parameter list, and a statement block. Given a property named P
, a setter declaration implicitly declares a method with the name set_P
with the same modifiers and parameter list as the property. If the type contains a declaration with that name, a compile-time error results, but the implicit declaration is ignored for the purposes of name binding.
If a parameter list is specified, it must have one member, that member must have no modifiers except ByVal
, and its type must be the same as the type of the property. The parameter represents the property value being set. If the parameter is omitted, a parameter named Value
is implicitly declared.
Note. Set
accessors have the same restriction on line placement that subroutines have. The beginning statement, end statement and block must all appear at the beginning of a logical line.
PropertySetDeclaration
: Attributes? AccessModifier? 'Set'
( OpenParenthesis ParameterList? CloseParenthesis )? LineTerminator
Block?
'End' 'Set' StatementTerminator
;
A property that specifies the modifier Default
is called a default property. Any type that allows properties may have a default property, including interfaces. The default property may be referenced without having to qualify the instance with the name of the property. Thus, given a class
Class Test
Public Default ReadOnly Property Item(i As Integer) As Integer
Get
Return i
End Get
End Property
End Class
the code
Module TestModule
Sub Main()
Dim x As Test = New Test()
Dim y As Integer
y = x(10)
End Sub
End Module
is equivalent to
Module TestModule
Sub Main()
Dim x As Test = New Test()
Dim y As Integer
y = x.Item(10)
End Sub
End Module
Once a property is declared Default
, all of the properties overloaded on that name in the inheritance hierarchy become the default property, whether they have been declared Default
or not. Declaring a property Default
in a derived class when the base class declared a default property by another name does not require any other modifiers such as Shadows
or Overrides
. This is because the default property has no identity or signature and so cannot be shadowed or overloaded. For example:
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
This program will produce the output:
MoreDerived = 10
Derived = 10
Base = 10
All default properties declared within a type must have the same name and, for clarity, must specify the Default
modifier. Because a default property with no index parameters would cause an ambiguous situation when assigning instances of the containing class, default properties must have index parameters. Furthermore, if one property overloaded on a particular name includes the Default
modifier, all properties overloaded on that name must specify it. Default properties may not be Shared
, and at least one accessor of the property must not be Private
.
If a property omits declaration of any accessors, an implementation of the property will be automatically supplied unless the property is declared in an interface or is declared MustOverride
. Only read/write properties with no arguments can be automatically implemented; otherwise, a compile-time error occurs.
An automatically implemented property x
, even one overriding another property, introduces a private local variable _x
with the same type as the property. If there is a collision between the local variable's name and another declaration, a compile-time error will be reported. The automatically implemented property's Get
accessor returns the value of the local and the property's Set
accessor that sets the value of the local. For example, the declaration:
Public Property x() As Integer
is roughly equivalent to:
Private _x As Integer
Public Property x() As Integer
Get
Return _x
End Get
Set (value As Integer)
_x = value
End Set
End Property
As with variable declarations, an automatically implemented property can include an initializer. For example:
Public Property x() As Integer = 10
Public Shared Property y() As New Customer() With { .Name = "Bob" }
Note. When an automatically implemented property is initialized, it is initialized through the property, not the underlying field. This is so overriding properties can intercept the initialization if they need to.
Array initializers are allowed on automatically implemented properties, except that there is no way to specify the array bounds explicitly. For example:
' 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
An iterator property is a property with the Iterator
modifier. It is used for the same reason an iterator method (Section Iterator Methods) is used -- as a convenient way to generate a sequence, one which can be consumed by the For Each
statement. The Get
accessor of an iterator property is interpreted in the same way as an iterator method.
An iterator property must have an explicit Get
accessor, and its type must be IEnumerator
, or IEnumerable
, or IEnumerator(Of T)
or IEnumerable(Of T)
for some T
.
Here is an example of an iterator property:
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
Operators are methods that define the meaning of an existing Visual Basic operator for the containing class. When the operator is applied to the class in an expression, the operator is compiled into a call to the operator method defined in the class. Defining an operator for a class is also known as overloading the operator.
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'
;
It is not possible to overload an operator that already exists; in practice, this primarily applies to conversion operators. For example, it is not possible to overload the conversion from a derived class to a base class:
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
Operators can also be overloaded in the common sense of the word:
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
Operator declarations do not explicitly add names to the containing type's declaration space; however they do implicitly declare a corresponding method starting with the characters "op_". The following sections list the corresponding method names with each operator.
There are three classes of operators that can be defined: unary operators, binary operators and conversion operators. All operator declarations share certain restrictions:
-
Operator declarations must always be
Public
andShared
. ThePublic
modifier can be omitted in contexts where the modifier will be assumed. -
The parameters of an operator cannot be declared
ByRef
,Optional
orParamArray
. -
The type of at least one of the operands or the return value must be the type that contains the operator.
-
There is no function return variable defined for operators. Therefore, the
Return
statement must be used to return values from an operator body.
The only exception to these restrictions applies to nullable value types. Since nullable value types do not have an actual type definition, a value type can declare user-defined operators for the nullable version of the type. When determining whether a type can declare a particular user-defined operator, the ?
modifiers are first dropped off of all of the types involved in the declaration for the purposes of validity checking. This relaxation does not apply to the return type of the IsTrue
and IsFalse
operators; they must still return Boolean
, not Boolean?
.
The precedence and associativity of an operator cannot be modified by an operator declaration.
Note. Operators have the same restriction on line placement that subroutines have. The beginning statement, end statement and block must all appear at the beginning of a logical line.
The following unary operators can be overloaded:
-
The unary plus operator
+
(corresponding method:op_UnaryPlus
) -
The unary minus operator
-
(corresponding method:op_UnaryNegation
) -
The logical
Not
operator (corresponding method:op_OnesComplement
) -
The
IsTrue
andIsFalse
operators (corresponding methods:op_True
,op_False
)
All overloaded unary operators must take a single parameter of the containing type and may return any type, except for IsTrue
and IsFalse
, which must return Boolean
. If the containing type is a generic type, the type parameters must match the containing type's type parameters. For example,
Structure Complex
...
Public Shared Operator +(v As Complex) As Complex
Return v
End Operator
End Structure
If a type overloads one of IsTrue
or IsFalse
, then it must overload the other as well. If only one is overloaded, a compile-time error results.
Note. IsTrue
and IsFalse
are not reserved words.
The following binary operators can be overloaded:
-
The addition
+
, subtraction-
, multiplication*
, division/
, integral division\
, moduloMod
and exponentiation^
operators (corresponding method:op_Addition
,op_Subtraction
,op_Multiply
,op_Division
,op_IntegerDivision
,op_Modulus
,op_Exponent
) -
The relational operators
=
,<>
,<
,>
,<=
,>=
(corresponding methods:op_Equality
,op_Inequality
,op_LessThan
,op_GreaterThan
,op_LessThanOrEqual
,op_GreaterThanOrEqual
). Note. While the equality operator can be overloaded, the assignment operator (used only in assignment statements) cannot be overloaded. -
The
Like
operator (corresponding method:op_Like
) -
The concatenation operator
&
(corresponding method:op_Concatenate
) -
The logical
And
,Or
andXor
operators (corresponding methods:op_BitwiseAnd
,op_BitwiseOr
,op_ExclusiveOr
) -
The shift operators
<<
and>>
(corresponding methods:op_LeftShift
,op_RightShift
)
All overloaded binary operators must take the containing type as one of the parameters. If the containing type is a generic type, the type parameters must match the containing type's type parameters. The shift operators further restrict this rule to require the first parameter to be of the containing type; the second parameter must always be of type Integer
.
The following binary operators must be declared in pairs:
-
Operator
=
and operator<>
-
Operator
>
and operator<
-
Operator
>=
and operator<=
If one of the pair is declared, then the other must also be declared with matching parameter and return types, or a compile-time error will result. (Note. The purpose of requiring paired declarations of relational operators is to try and ensure at least a minimum level of logical consistency in overloaded operators.)
In contrast to the relational operators, overloading both the division and integral division operators is strongly discouraged, although not an error. (Note. In general, the two types of division should be entirely distinct: a type that supports division is either integral (in which case it should support \
) or not (in which case it should support /
). We considered making it an error to define both operators, but because their languages do not generally distinguish between two types of division the way Visual Basic does, we felt it was safest to allow the practice but strongly discourage it.)
Compound assignment operators cannot be overloaded directly. Instead, when the corresponding binary operator is overloaded, the compound assignment operator will use the overloaded operator. For example:
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
Conversion operators define new conversions between types. These new conversions are called user-defined conversions. A conversion operator converts from a source type, indicated by the parameter type of the conversion operator, to a target type, indicated by the return type of the conversion operator. Conversions must be classified as either widening or narrowing. A conversion operator declaration that includes the Widening
keyword introduces a user-defined widening conversion (corresponding method: op_Implicit
). A conversion operator declaration that includes the Narrowing
keyword introduces a user-defined narrowing conversion (corresponding method: op_Explicit
).
In general, user-defined widening conversions should be designed to never throw exceptions and never lose information. If a user-defined conversion can cause exceptions (for example, because the source argument is out of range) or loss of information (such as discarding high-order bits), then that conversion should be defined as a narrowing conversion. In the example:
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
the conversion from Digit
to Byte
is a widening conversion because it never throws exceptions or loses information, but the conversion from Byte
to Digit
is a narrowing conversion since Digit
can only represent a subset of the possible values of a Byte
.
Unlike all other type members that can be overloaded, the signature of a conversion operator includes the target type of the conversion. This is the only type member for which the return type participates in the signature. The widening or narrowing classification of a conversion operator, however, is not part of the operator's signature. Thus, a class or structure cannot declare both a widening conversion operator and a narrowing conversion operator with the same source and target types.
A user-defined conversion operator must convert either to or from the containing type -- for example, it is possible for a class C
to define a conversion from C
to Integer
and from Integer
to C
, but not from Integer
to Boolean
. If the containing type is a generic type, the type parameters must match the containing type's type parameters. Also, it is not possible to redefine an intrinsic (i.e. non-user-defined) conversion. As a result, a type cannot declare a conversion where:
-
The source type and the destination type are the same.
-
Both the source type and the destination type are not the type that defines the conversion operator.
-
The source type or the destination type is an interface type.
-
The source type and destination types are related by inheritance (including
Object
).
The only exception to these rules applies to nullable value types. Since nullable value types do not have an actual type definition, a value type can declare user-defined conversions for the nullable version of the type. When determining whether a type can declare a particular user-defined conversion, the ?
modifiers are first dropped off of all of the types involved in the declaration for the purposes of validity checking. Thus, the following declaration is valid because S
can define a conversion from S
to T
:
Structure T
...
End Structure
Structure S
Public Shared Widening Operator CType(ByVal v As S?) As T
...
End Operator
End Structure
The following declaration is not valid, however, because structure S
cannot define a conversion from S
to S
:
Structure S
Public Shared Widening Operator CType(ByVal v As S) As S?
...
End Operator
End Structure
Because the set of operators that Visual Basic supports may not exactly match the set of operators that other languages on the .NET Framework, some operators are mapped specially onto other operators when being defined or used. Specifically:
-
Defining an integral division operator will automatically define a regular division operator (usable only from other languages) that will call the integral division operator.
-
Overloading the
Not
,And
, andOr
operators will overload only the bitwise operator from the perspective of other languages that distinguish between logical and bitwise operators. -
A class that overloads only the logical operators in a language that distinguishes between logical and bitwise operators (i.e. a languages that uses
op_LogicalNot
,op_LogicalAnd
, andop_LogicalOr
forNot
,And
, andOr
, respectively) will have their logical operators mapped onto the Visual Basic logical operators. If both the logical and bitwise operators are overloaded, only the bitwise operators will be used. -
Overloading the
<<
and>>
operators will overload only the signed operators from the perspective of other languages that distinguish between signed and unsigned shift operators. -
A class that overloads only an unsigned shift operator will have the unsigned shift operator mapped onto the corresponding Visual Basic shift operator. If both an unsigned and signed shift operator is overloaded, only the signed shift operator will be used.