Defining Shared Constructors


Visual Basic .NET Unleashed
By Paul Kimmel
Table of Contents
Chapter 11.  Shared Members

Defining Shared Constructors

Visual Basic .NET implements constructors as Sub New. By prefixing a constructor with the Shared modifier, you can implement a shared constructor.

Shared constructors are used to initialize shared members to their default values implicitly and can be implemented to perform additional initialization of shared members. Shared constructors are run implicitly after your program loads and are guaranteed to run only once before any instances of a type containing a shared constructor are created and before any shared members are accessed. Shared constructors are also run before derived types are used.

You can't overload shared members. Shared constructors cannot call other constructors, cannot take parameters, and cannot be used to overload instance constructors. The declaration of a shared constructor is always in the following format:

 Shared Sub New() End Sub 

Implicitly, this is a public member. The shared constructor in the preceding fragment is distinct from an instance constructor:

 Sub New() End Sub 

The implicit nature of shared constructors makes them excellent devices for initializing shared members and simulating Singleton objects by using class methods and a shared constructor.

Shared Constructors and Singletons

A Singleton object is an object that is only created once. There are certain resources of which you generally only need one when a program is running and of which only one is accessible to the running program. A good candidate for a Singleton object is the registry.

Programs generally update the registry to persist state information about an application, such as paths for data files, login information, last Web site visited, or file opened. The registry is a good place to store this information. And, a good strategy for implementing a mechanism to store state information is to wrap the Registry class that ships with .NET in a class that exposes named state properties. In this way, the state information is accessible anywhere in your program, including an Options form, and is easy to access and modify. The alternative is to create, initialize, and release the object each time you want to modify the state information.


A further benefit of encapsulating the registry in a class is that you change how and where state information is saved without having to modify the code that uses it. Define an interface that allows users to get and set state information, and you can change the underlying implementation at any time. This is useful during development. Sometimes it's easier to save state information to an INI file while you're developing the code and switch to the registry before releasing your product. Using a wrapper class insulates the rest of your code from this strategy.

Because there is only one registry, the registry is a good example of something that can be treated as a Singleton object. Making the registry even easier to use can be accomplished via a shared constructor in a class that encapsulates a registry object and has shared setters and getters (property methods) for reading and writing registry information. The next section contains an example.

Shared Constructor Example

Shared constructors can be used to emulate Singleton objects. Singleton objects are generally implemented via a shared method that ensures that only one instance of the object is created. This is accomplished by making the constructor private. A private constructor means that consumers can't create instances of the class. However, if there is a public shared method of the same class, it can call the private constructor and subsequently create instances. Using a Static counter or a shared field, the public shared method can ensure that only one instance of the Singleton exists.

Singletons are used when it's beneficial or important that only one instance of an object exists. The technique is also used for convenience. What better way to simplify code than to inhibit the unnecessary creation of objects?

Using a shared constructor and a Private constructor to prevent consumers from creating unnecessary objects, Listing 11.6 demonstrates two kinds of persistency in the limited context of the text editor; one half of the code demonstrates using the Registry and the other half demonstrates using a HybridDictionary object. (The text editor itself is intentionally only partially complete. The example solution is on this book's Web site (see in DualModeStateExample.sln).


The Registry class will be used when the DualModeStateExample.exe is distributed, and a HybridDictionary will be used during development. (Refer to the code listing for the implementation of the persisted HybridDictionary.)

The Options class uses the Registry to save state information if we aren't configured for DEBUG mode, and uses a persisted HybridDictionary class if we are still testing.

Listing 11.6 Both implementations of persisted application options
  1:  ' Options.vb - Implements dual mode state persistency using  2:  '    a shared constructor and properties  3:  ' Copyright  2001. All Rights Reserved.  4:  ' Written by Paul Kimmel.  5:   6:  Imports Microsoft.Win32, System.Collections.Specialized, System.IO  7:   8:  #If DEBUG = False Then  9:   10:  Public Class Options  11:   12:  Private Sub New()  13:  End Sub  14:   15:  Private Enum Keys  16:  LastFileName  17:  End Enum  18:   19:  Public Shared Property LastFileName() As String  20:  Get  21:  Return ReadString(Keys.LastFileName.ToString)  22:  End Get  23:  Set(ByVal Value As String)  24:  WriteString(Keys.LastFileName.ToString, Value)  25:  End Set  26:  End Property  27:   28:  Private Shared Function Subkey() As String  29:  Return "Software\ " & "VB.NET UNLEASHED"  30:  End Function  31:   32:  Private Shared Function OpenKey() As RegistryKey  33:   34:  Return Registry.LocalMachine.OpenSubKey(Subkey(), True)  35:   36:  End Function  37:   38:  Private Shared Function ReadString(ByVal Key As String, _  39:  Optional ByVal Value As String = "") As String  40:  Try  41:  Return OpenKey().GetValue(Key, Value)  42:  Catch  43:  Return Value  44:  End Try  45:  End Function  46:   47:  Private Shared Sub WriteString(ByVal Key As String, _  48:  ByVal Value As String)  49:  Try  50:  OpenKey().SetValue(Key, Value)  51:  Catch  52:  Registry.LocalMachine.CreateSubKey(Subkey())  53:  OpenKey().SetValue(Key, Value)  54:  End Try  55:  End Sub  56:   57:   58:  End Class  59:   60:  #Else  61:   62:  Public Class Options  63:   64:  Private Sub New()  65:   66:  End Sub  67:   68:  Private Enum Keys  69:  LastFileName  70:  End Enum  71:   72:  Private Shared Loading As Boolean  73:  Private Shared FDictionary As HybridDictionary  74:   75:  Shared Sub New()  76:  FDictionary = New HybridDictionary()  77:  LoadDictionary()  78:  End Sub  79:   80:  Public Shared Property LastFileName() As String  81:  Get  82:  Return ReadString(Keys.LastFileName.ToString)  83:  End Get  84:  Set(ByVal Value As String)  85:  WriteString(Keys.LastFileName.ToString, Value)  86:  End Set  87:  End Property  88:   89:   90:  Private Shared Function ReadString(ByVal Key As String, _  91:  Optional ByVal Value As String = "")  92:   93:  If (FDictionary.Item(Key) Is Nothing) Then Return Value  94:  Return FDictionary.Item(Key)  95:   96:  End Function  97:   98:  Private Shared Sub WriteString(ByVal Key As String, _  99:  ByVal Value As String)  100:  FDictionary.Remove(Key)  101:  FDictionary.Add(Key, Value)  102:  SaveDictionary()  103:  End Sub  104:   105:  Private Shared Function GetOptionsFile() As String  106:  Return Application.StartupPath & "\ options.ini"  107:  End Function  108:   109:  Private Shared Sub SaveDictionary()  110:  If (Loading) Then Exit Sub  111:  Dim Writer As StreamWriter = _  112:  File.CreateText(GetOptionsFile())  113:  Try  114:  Writer.WriteLine(Keys.LastFileName.ToString & "=" & _  115:  FDictionary.Item(Keys.LastFileName.ToString))  116:  Finally  117:  Writer.Close()  118:  End Try  119:  End Sub  120:   121:  Private Shared Sub LoadDictionary()  122:  If (Not File.Exists(GetOptionsFile())) Then Exit Sub  123:   124:  Dim Reader As StreamReader = _  125:  File.OpenText(GetOptionsFile())  126:   127:  Try  128:  Loading = True  129:  Dim Line As String = Reader.ReadLine()  130:  LastFileName = Mid(Line, InStr(Line, "=") + 1)  131:   132:  Finally  133:  Reader.Close()  134:  Loading = False  135:  End Try  136:   137:  End Sub  138:   139:  End Class  140:   141:  #End If 

Listing 11.6 implements two versions of the Options class. Both versions have the exact same interface, so consumers aren't affected adversely when the Configuration information changes and we switch from persisting application settings to an OPTIONS.INI file versus the registry. Using an OPTIONS.INI file is a safer alternative and is more convenient when doing initial development.

Each version of the Options class uses a Private constructor, ensuring that consumers don't create instances. The classes are defined in such a manner as not to require instances, that is, all members are shared. (Unless mentioned, further discussion of the Options class applies to both examples.) Options uses an enumeration to make reading and writing keys easy to follow. Although the class only persists the LastFileName value, implementing additional state information simply requires adding enumerated values to the Keys enum and implementing the shared properties. LastFileName is a suitable model for any shared state information. Each shared property delegates reading and writing to the ReadString and WriteString methods. Because everything except the property is private, this class will be easy for consumers to use even though it required a little extra effort to write.

The biggest difference between the two versions of Options is that the registry version has to create and open registry keys. It's assumed that the registry information exists unless an exception occurs (starting at line 51); in the event of an exception, the key is created and the write is attempted again.

The HybridDictionary (defined in the System.Collections.Specialized namespace) example stores the keys in name /value pairs and uses the File, StreamReader, and StreamWriter classes defined in System.IO to manage file persistence. Only the HybridDictionary version requires a shared constructor (lines 75 to 78), which is responsible for creating an instance of the dictionary. Keep in mind that you never have to call the Shared constructor, and the compiler will generate code guaranteeing that the Shared constructor is called before any other code uses the class.

Listing 11.7 demonstrates a consumer that uses the Options class. The consumer is indifferent to which Options class is used. Switching back and forth between implementations of the Options class based on configuration settings has no impact or revisionary requirements on the consumer.

Listing 11.7 Form1 represents a consumer of the Options class
  1:  Public Class Form1  2:  Inherits System.Windows.Forms.Form  3:   4:  [ Windows Foprm Designer generated code ]  5:   6:  Private Sub MenuItem5_Click(ByVal sender As System.Object, _  7:  ByVal e As System.EventArgs) Handles MenuItem5.Click  8:  Application.Exit()  9:  End Sub  10:   11:  Private Sub MenuItem2_Click(ByVal sender As System.Object, _  12:  ByVal e As System.EventArgs) Handles MenuItem2.Click  13:  ShowOpenFile()  14:  End Sub  15:   16:  Private Sub AddRecentFile(ByVal FileName As String)  17:  Options.LastFileName = FileName  18:  AddSubMenu(FileName)  19:  End Sub  20:   21:  Private Sub OnOpen(ByVal sender As Object, ByVal e As System.EventArgs)  22:  DoOpenFile(CType(sender, MenuItem).Text)  23:  End Sub  24:   25:  Private Sub AddSubMenu(ByVal RecentFile As String)  26:  MenuItemRecent.MenuItems.Add(RecentFile, AddressOf OnOpen)  27:  End Sub  28:   29:  Private Sub DoOpenFile(ByVal FileName As String)  30:  RichTextBox1.LoadFile(FileName, _  31:  RichTextBoxStreamType.PlainText)  32:  End Sub  33:   34:  Private Sub OpenFile(ByVal FileName As String)  35:  DoOpenFile(FileName)  36:  AddRecentFile(OpenFileDialog1.FileName)  37:  End Sub  38:   39:  Private Sub ShowOpenFile()  40:  OpenFileDialog1.Filter = "Plain Text (*.txt)*.txt"  41:  OpenFileDialog1.InitialDirectory = "C:\ "  42:   43:  If (OpenFileDialog1.ShowDialog() <> DialogResult.OK) Then _  44:  Exit Sub  45:   46:  OpenFile(OpenFileDialog1.FileName)  47:  End Sub  48:   49:  Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)  50:  If (Options.LastFileName = "") Then Exit Sub  51:  AddSubMenu(Options.LastFileName)  52:   53:  End Sub  54:  End Class 

Listing 11.7 defines a form (with the generated code hidden) that implements a simple text editor. The application allows the user to open a file. The file is loaded into a RichTextBox control. The Recent menu is dynamically updated to contain submenus referring to previously opened files, and Options.LastFileName is updated. On subsequent runs, LastFileName is added to the Recent menu when the program is started (refer to the OnLoad method on lines 49 to 53).

The very short functions demonstrate optimum reuse. AddSubMenu is reused in AddRecentFile and OnLoad, and DoOpenFile (which loads the file into the RichTextBox) is reused in the OpenFile method and OnOpen event handler.

Listing 11.7 demonstrates several techniques that you might find practical. Line 26 demonstrates how to add submenus dynamically and associate event handlers with those menus . Line 22 demonstrates an example of using a dynamic menu caption as an argument to a method and dynamic type conversion. Lines 30 and 31 demonstrate how to load the contents of a file into a RichTextBox control; you could create the text editor by using additional properties defined in the RichTextBox control, like SaveFile. And, there are several examples of the refactoring "Extract Method," reducing the code listing considerably. An example of refactoring was extracting DoOpenFile from OpenFile on lines 2937.


Visual BasicR. NET Unleashed
Visual BasicR. NET Unleashed
Year: 2001
Pages: 222 © 2008-2017.
If you may any questions please contact us: