Refactorable Settings in Visual Studio, Part 1
/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:
- Reading and writing settings to disk - all of the serialization/deserialization is handled for you, as are the correct locations for the settings.
- 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.
- 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.
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
.
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.