When I first read about extension methods being added to C# (which was a long time before I actually started using C#) I thought they were a liability. Superficially it seemed to be “an accident waiting to happen” because it’s hard enough trying to understand the responsibilities of a class or interface when you have the code in front of you. With extension methods there could be behaviours bolted-on by a third party that you just can’t see.
Once more the classic quote from Spider-Man “with great power comes great responsibility” rears it’s head. Used judiciously extension methods are a great way to graft on functionality that a class author hasn’t provided, or a way to provide a Fluent Interface to a particular problem domain. I would hazard a guess that the String class has had significantly more extension methods than any other as I personally have a written a few obvious ones such as IsEmpty and JoinUsing.
Extending concrete classes probably makes a lot of sense, but extension methods can also be used to extend interfaces. That sounds a but more surreal – how do you extend an abstraction in a concrete way that applies to all implementations of that abstraction? There is nothing magical it’s really just turning the idea of an abstract base class on it’s head…
An Initial Configuration Mechanism
All non-trivial systems end up with some common configuration mechanism and my current one is no exception. Whilst bootstrapping the initial components I didn’t want to get into the nitty-gritty of the .Net ConfigurationManager class so I introduced a simple interface:-
public IConfiguration
{
string Setting(string name);
string Setting(string name, string defaultValue);
}
The first overload will return the setting value or an empty string if there is no setting with that name[*]. The second overload returns the default value specified when there is no value to return. The natural implementation for the first overload then becomes:-
public string Setting(string name)
{
return Setting(name, “”);
}
I tossed and turned about leaving the first overload out as it forces the implementer of IConfiguration to implement something that is really just syntactic sugar. Initially the only way round this that I knew was to introduce a default implementation so that the implementer just had to derive from it to get the syntactic sugar method:-
public abstract class ConfigurationDefault
: IConfiguration
{
public string Setting(string name)
{
return Setting(name, “”);
}
}
. . .
public class XmlConfiguration : ConfigurationDefault
{
. . .
}
The use of abstract base classes is a tried-and-tested approach to reusing code in a polymorphic class hierarchy and so I was happy. At least until some new requirements came along.
Improvements to the Configuration Mechanism
The implementation served us well to start with but I knew from experience that there are certain behaviours I’ve found really useful, such as being able to specify environment variables like %ProgramFiles% and %TEMP% in file paths that are then expanded on demand. By now I had started getting to grips with extension methods and quickly realised that this was a behaviour that was also independent of any IConfiguration implementation, so I wrote it as an extension method:-
public static class IConfigurationExtensions
{
public string PathSetting(this IConfiguration
configuration, string name, string defaultValue)
{
var setting = Setting(name, defaultValue);
// Do substitutions
. . .
}
}
One Method to Serve Them All
After adding this support and a further behaviour to allow arbitrary substitution of other settings I found that it was all too easy to mess one up and get odd behaviour. We decided that the silent failure when doing substitutions was a bad idea and that instead we’d prefer to throw an exception if a setting or environment variable did not exist. But that caused a problem because the original interface was not designed to allow the absence of a setting to be detected, it just returned an empty string or the default value and I didn’t want to get into passing a random default value just to detect if the setting existed or not.
At first I thought that I should just extend the interface with a TryXxx() style method that would return true or false along with the value if it existed. But it was whilst thinking through the ramifications of this change that I realised that this was in fact the only method that I needed in the interface:-
public IConfiguration
{
bool TryGetSetting(string name, out string value);
}
All the other methods could be written as extension methods implemented in terms of this one interface method:-
public static class IConfigurationExtensions
{
public string Setting(this IConfiguration
configuration, string name)
{
return Setting(name, “”);
}
public string Setting(this IConfiguration
configuration, string name, string defaultValue)
{
string value;
if (configuration.TryGetSetting(name, out value))
return value;
return defaultValue;
}
public string PathSetting(this IConfiguration
configuration, string name, string defaultValue)
{
. . .
}
}
This also meant that I could drop the abstract base class that provided the default implementations of the syntactic sugar methods.
OK, so I haven’t exactly turned the abstract base class on its head, I’ve really swapped an abstract base class for a static helper class and with that I have lost the ability to re-implement the methods another way in the derived classes. But the whole point of the base class was not to enable polymorphism but to provide reusable helper methods – inheritance was just a means to a different end.
My original fears about extension methods lay unfounded and instead have provided a much cleaner solution to the problem of extending an interface to provide syntactic sugar (e.g. a fluent interface) where there previously was none. This revelation will no doubt be nothing new to experienced LINQ users because that’s exactly what it provides for IEnumerable, but for me it’s added a new dimension to how I think about and design my own interfaces.
[*] Yes I could have returned ‘null’ but my C++ roots were telling me that I should return a value not an empty reference (or NULL pointer in the C++ world). I still struggle with this duality of the String type – null vs “” – although as I start to use nullable value types more in the Data Access Layer I feel I’m getting to grips with it slowly.
Hi Chris, i have an interface that has a method without any parameters and this interface is implemented by two concrete classes, so what i want is to overload this method inside the interface to accept a parameter using an extension method and i want to use it only in one class how can i accomplish that ?
ReplyDeleteThank you and sorry for my English.
You can't, if I understand you correctly. An interface by definition is more generalised than a class and so an extension method based on the interface has to apply to all classes that implement it.
ReplyDeleteIf only one class needs the one-parameter overload then only add the method to the concrete class. It sounds to me like the interface probably needs the one-parameter method and the extension method is the no-parameter overload that calls the one-parameter version with a default value.