9.3.2 Shared Constructors
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
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 any instance of a class type is created.
- Shared constructors are run before any instance members of a structure type are accessed, or before any constructor of a structure type is explicitly called. Calling the implicit parameterless constructor created for structures will not cause the shared constructor to run.
- Shared constructors are run before any of the type's shared members are referenced.
- Shared constructors are run before any types that derive from the type are loaded.
- A shared constructor will not be run more than once during a single execution of a program.
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:
Imports System 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
Init B Init A A.F B.F
By contrast, the following example produces predictable output, because the Shared constructor for the class
A must execute before the Shared constructor for the class
B, which derives from
Imports System Module Test Sub Main() Console.WriteLine("1") B.G() Console.WriteLine("2") 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 A 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
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.