Refactorable Settings in Visual Studio, Part 1

(Part 2, Part 3)

One of the really nice features of Visual Studio when developing desktop applications are the built-in Application Settings generators. Basically, VS gives you an easy way to handle application-level and user-level settings and preferences in your application. The built-in interface, generators, and base classes handle several things for you:

  1. Reading and writing settings to disk - all of the serialization/deserialization is handled for you, as are the correct locations for the settings.
  2. User settings vs. application settings - you don't have to worry about keeping separate settings files per user (or where on disk to put them); all of that is handled for you.
  3. Persisting settings across application updates - this pretty much just works.

Creating settings is easy - you can either use the built-in editor (which is convenient, though I admit it could use some work) or edit the XML by hand. VS provides an interface for generating the initial XML (in the project property pages), and once it's set up any changes to the XML document trigger a custom tool which generates the C# code for your settings class.

example of settings gui

Don't let all the "Windows Forms" stuff in the MSDN link above fool you - the settings classes are perfectly usable in any type of non-web application (console, Windows Service, WPF, whatever). In fact, they work really well with XAML binding (because ApplicationSettingsBase implements INotifyPropertyChanged). Here's a chunk of XAML showing a boolean setting AutoCheckUpdates bound to a checkbox:

<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
    <CheckBox 
        IsChecked="{Binding Source={x:Static local:Settings.Default}, Path=AutoCheckUpdates, Mode=TwoWay}" />
    <TextBlock Text="Automatically check for updates" />
</StackPanel>

The binding is set to two-way, which means that the checkbox will display the current value from Settings and that changes to the checkbox will update the value in the Settings instance. This makes settings dialogs ridiculously easy to create.

However, the generate Settings classes do have a significant weakness: the events are not refactor-friendly. Let me explain with an example. We'll use the AutoCheckUpdates setting from the XAML example. Here's what that setting might look like in the code generated by Visual Studio:

[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool AutoCheckUpdates {
    get {
        return ((bool)(this["AutoCheckUpdates"]));
    }
    set {
        this["AutoCheckUpdates"] = value;
    }
}

For most refactoring purposes, this is fine. Let's say that elsewhere I have the following code:

var check = Settings.Default.AutoCheckUpdates;

A few months later I decide to change the name of the setting to make it more descriptive (this is a contrived example; don't worry whether the new name is actually more descriptive). So I change the setting name to CheckForUpdateOnStart.

example of settings gui

From a refactoring standpoint, we don't have any problem because the line of code above will now fail to compile; I'll have to change it to

var check = Settings.Default.CheckForUpdateOnStart;

in order for the project to build. This is behavior is refactor-friendly. All is well.

But there's a potential issue lurking in the generated settings classes. The problem lies with how ApplicationSettingsBase handles the SettingChanging event. SettingChanging is useful for things like validation - it fires before the setting change is committed and supports cancellation. If we wanted something to occur when the user changes the AutoCheckUpdates setting, we could do something like this:

Settings.Default.SettingChanging +=
    (sender, eventArgs) =>
    {
        if (eventArgs.SettingName == "AutoCheckUpdates")
        {
            // Validate here - maybe this setting is 
            // something the user needs to confirm or see
            // a warning about
        }
    };

The eventArgs here are of type SettingChangingEventArgs, which only gives us the SettingName property with which to identify the setting which is being changed. This becomes a major problem for refactoring. Let's again say that we're changing the name of the setting to CheckForUpdateOnStart.

The code in the event handler still compiles. It will never work - the code inside the if block will never be hit - but it will compile just fine.

This is a problem if you want to be able to safely refactor code which deals with the SettingChanging event. Ideally, we'd like to be able to validate (or otherwise react to) changing settings without having to worry that a hard-coded string is going to bite us when we change the name of a setting. In the next post I'll talk about some ways to make that possible.