Use Interfaces for Metadata and Comments
If you’re using XML Comments for intellisense purposes, or are making heavy use of attribute-based metadata in your classes, you’ve likely found that these have a tendency to bloat your code and make it more difficult to follow. For example, consider this simple configuration section handler:
1: public class DemoSettings : ConfigurationSection, IDemoSettings
2: {
3: private static readonly DemoSettings settings =
4: ConfigurationManager.GetSection("DemoSettings") as DemoSettings;
5:
6: public static DemoSettings Settings
7: {
8: get { return settings; }
9: }
10:
11: [ConfigurationProperty("sessionAttendees"
12: , DefaultValue = 200
13: , IsRequired = false)]
14: [IntegerValidator(MinValue = 1
15: , MaxValue = 10000)]
16: public int SessionAttendees
17: {
18: get { return (int) this["sessionAttendees"]; }
19: set { this["sessionAttendees"] = value; }
20: }
21:
22:
23: [ConfigurationProperty("title"
24: , IsRequired = true)]
25: [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;’\"|\\")]
26: public string Title
27: {
28: get { return (string) this["title"]; }
29: set { this["title"] = value; }
30: }
31: }
This is an example of a custom configuration section handler which I’ve written about previously. I highly recommend using such configuration sections instead of the built-in appSettings configuration section, for a number of reasons. But note that the use of attributes alone adds quite a bit of bloat to this file, and it doesn’t even make use of any XML comments at this point in time.
I’ve found it to be worthwhile to use interfaces to avoid direct dependencies on configuration files, which helps achieve loose coupling and enable easier testing of a variety of configuration options. Another advantage I’ve recently discovered is the use of the interface to hold metadata and comments in a single location (following the DRY principle), which keeps implementations of the interface much cleaner.
Consider this interface for the above configuration section:
1: public interface IDemoSettings
2: {
3: /// <summary>
4: /// Should indicate how many people are in the session
5: /// </summary>
6: [ConfigurationProperty("sessionAttendees",
7: DefaultValue = 200,
8: IsRequired = false)]
9: [IntegerValidator(MinValue = 1,
10: MaxValue = 10000)]
11: int SessionAttendees { get; set; }
12:
13: /// <summary>
14: /// The name of the presentation
15: /// </summary>
16: [ConfigurationProperty("title",
17: IsRequired = true)]
18: [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;’\"|\\")]
19: string Title { get; set; }
20: }
With this in place, we can eliminate the attributes from the class file and still get the validation these attributes provide. Further, the XML comments will appear in intellisense when these properties are used in our code. The adjusted configuration section handler now looks like this:
1: public class DemoSettings : ConfigurationSection, IDemoSettings
2: {
3: private static readonly DemoSettings settings =
4: ConfigurationManager.GetSection("DemoSettings") as DemoSettings;
5:
6: public static DemoSettings Settings
7: {
8: get { return settings; }
9: }
10:
11: public int SessionAttendees
12: {
13: get { return (int) this["sessionAttendees"]; }
14: set { this["sessionAttendees"] = value; }
15: }
16:
17: public string Title
18: {
19: get { return (string) this["title"]; }
20: set { this["title"] = value; }
21: }
22: }
It’s about 30% smaller (lines of code) on a class with only 2 properties. If this configuration section had many more properties, or many more attributes, or was using XML comments to start with, the reduction would be much greater.
Here’s what Visual Studio 2010 provides when referencing the class now, with the XML comments stored in the interface:
And of course, the attributes still kick in if we don’t have required attributes in the configuration file, or if our values are outside of specified ranges.
Summary
In general, interfaces don’t get enough use in most applications. This is just one more reason why interfaces are useful as a means of cleaning up code and keeping a proper separation between the abstraction and the contract of what a class should do, and the actual implementation of that abstraction and contract.