Code Focused

Remember User Settings

VB's My.Settings class makes it easy for you to remember user-preferred settings; also, what a quirky result in VB's debug window reveals about how its compiler and the .NET runtime work together.

Technology Toolbox: Visual Basic

One quintessential feature of a user-friendly application is that it remembers the way you like things.

Visual Basic 8 (AKA VB 2005) made storing and retrieving user settings extremely easy by providing developers with a settings designer and the My.Settings class. Out of the box, you can store and retrieve any XML serializable data as strongly typed data through the My.Settings class.

My.Settings gives you programmatic access to strongly typed settings, as well as the ability to bind directly to Form or Control properties at design time through their Properties window (Figure 1).

Settings don't have to be single values; they can also be complex objects. For example, if you were building a stock ticker, you could store a dynamic list of stocks the user wants to watch. Assume you have a Stock class:

Public Class Stock
	Public Property Symbol() As String
		...
	End Property
	Public Property Price() As Decimal
		...
	End Property
End Class

The Settings designer doesn't support generics directly, so you can't add a List(Of Stock) to the designer. Instead, you have to define a concrete class for the list of stocks:

Public Class StockList
	Inherits List(Of Stock)
End Class

A concrete type for the list enables you to add a "stocks" setting of type StockList. Scroll down in the type drop down, select browse, and then type in the full name such as WindowsApplication1.StockList.

You need to be aware of at least two problems if the type is defined in the application assembly. One problem is the designer can behave strangely at times when it comes to setting the default value. For example, the default value can disappear after you edit it. A second problem is that you have a good chance of getting a redundant reference to the application assembly from the application itself. The reference problem is minor; all you need to do is delete the reference from the project's References. However, the default value problem can be tiresome.

You can avoid those problems if you put your types definitions into another assembly and reference that. Later, you can move them back or use ILMerge.

The settings designer and its generated code are robust and extensible, apart from the minor nuisances you encounter when attempting to work with types defined in the same assembly. Settings are persisted using a provider model, so you can substitute your own provider on a setting-by-setting basis. This allows you to use the My.Settings abstraction to separate your consuming code from the storage mechanism. You might want to change the storage mechanism you use for a handful of reasons: when saving large files separately; when performing binary serialization instead of XML serialization; for encryption or security; or to allow for sharing of settings between applications through the registry, a database, or another file-share location.

Creating your own provider is easy. Simply add a reference to System.Configuration and derive from System.Configuration.SettingsProvider. Override GetPropertyValues and SetPropertyValues, and then provide your own retrieval and saving (respectively). This requires working with SettingsPropertyValue objects, which allow you to work either with the SerializedValue or the actual PropertyValue (Listing 1).

Once you create your own provider, you can use the Properties window to specify that a setting should use your provider. Select your setting in the settings designer, then specify the Provider in the properties window (Figure 2).

The great part about My.Settings is that it is easy to use and extend.

VB Internals: How Shared Constructors Work
A fellow MVP brought it to my attention recently that Guid.Empty sometimes shows "Nothing" in the debugger window, while other times it displays "{System.Guid}."

My first thought was that this is related to the way VB treats default value types as equal to Nothing. But the real cause derives from the way Shared fields are instantiated. When you declare a Shared field in a Structure or Class and assign to it, the compiled code puts the assigning code into the Shared constructor.

Consider this short code snippet:

Public Structure IndexItem
	Public Shared Minimum As Int32 = -1
	Public Index As Int32
End Structure

VB .NET compiles this snippet to this:

Public Structure IndexItem
	Public Shared Minimum As Int32
	Public Index As Int32

	Shared Sub New()
		Minimum = -1
	End Sub
End Structure

At first glance, it appears as though Minimum will always be -1 by the time you instantiate an instance of this Structure. However, it's possible to write code that proves this assumption untrue:

	Dim idx As New IndexItem

If you set a debugger break point on this code, you see the value of Minimum is 0 in the first case, but -1 in the second. The reason for this is when the compiler adds the code to the Shared Sub New, it marks the Class or Structure as beforefieldinit. The beforefieldinit flag tells the runtime that it doesn't need to call the Shared constructor until a Shared field is called upon. The runtime can then decide when to call the constructor, whether immediately before the first Shared field is accessed, or at some point previous to that.

The benefit of beforefieldinit is that code that isn't required to run can be delayed indefinitely in some cases. This is of great value when the field triggers an expensive resource.

If your code includes a Shared Sub New (whether or not the sub has code in it), the compiler doesn't include the beforefieldinit flag, and all the field initialization happens before the compiler creates the first instance.

This explains part of what you see in the debugger window. One more missing part: Reading the Shared field with the debugger doesn't prompt the runtime to trigger the beforefieldinit field initialisations. Taken together, these facts explain why you see 0 in the first case and -1 in the second case of IndexItem.

But you might wonder why the debugger window displays "Nothing" for Guid.Empty. This comes down to the way intrinsic value types are treated compared to other value types. Intrinsic types basically skip the need for initobj, whereas a more complex value type would appear to be an invalid pointer until initobj is called. Hence, VB displays Guid.Empty as Nothing, whereas C# displays it as an "invalid pointer."

There you have it. If you don't incude a Shared Sub New, then the class gets the beforefieldinit flag applied to it. This is a dual-edged sword. On the one hand, you can look at it as an optimization. On the other, it makes some values appear unitialized when debugging.

About the Author

Bill McCarthy is an independent consultant based in Australia and is one of the foremost .NET language experts specializing in Visual Basic. He has been a Microsoft MVP for VB for the last nine years and sat in on internal development reviews with the Visual Basic team for the last five years where he helped to steer the languageā€™s future direction. These days he writes his thoughts about language direction on his blog at http://msmvps.com/bill.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube