Practical .NET
Leveraging Interfaces and Extension Methods To Extend Your Classes
Extension methods provide a great way for extending a class functionality -- but it's interfaces that let you use those methods anywhere you want.
Extension methods let you add functionality to an existing class without having to modify the class's code, supporting Vogel's first law of software maintenance: "You don't screw with working code." And, unlike using inheritance to extend a class, with extension methods you don't have to replace the class you're extending with a new class: The Microsoft .NET Framework automatically attaches your extension method to whatever existing class you want the extension method to be part of.
But it's actually wrong for me to say that extension methods are attached to classes: Extension methods are really attached to interfaces, which makes extension methods an even more powerful (and flexible) tool for incorporating functionality into your application. I'm going to demonstrate what that means in this column.
If you're new to extension methods, see "Extension Method Basics" later in this article.
The Problem with Extension Methods
Imagine, for instance, that I have Customer class with a LogInfo method that writes out critical information about the Customer class (in real life, this information would go in a log file, but I'll just write to the console for this example). That method might look something like this:
Public Sub LogInfo()
Console.WriteLine(Me.Name & ":" Me.KeyData)
End Sub
As useful as that method is, it exists in only one class. The method would be a lot more useful if it was an extension method so I could use it from other classes. Rewriting the method as an extension method that will still work with the Customer class gives me this code:
Public Module PHVExtensions
<System.Runtime.CompilerServices.Extension()>
Public Sub LogInfo(ByVal logSubject As Customer)
Console.WriteLine(logSubject.Name & ": " logSubject.KeyData)
End Sub
End Module
Unfortunately, this method will only work with classes that expose the Customer interface (that's the Customer class and any class that inherits from Customer). Inheritance makes all of those classes "look alike" to my extension method.
However, I want to do logging with many more classes than those that inherit from my Customer class. That makes it difficult to specify to which interface the LogInfo extension method should tie itself. I could tie my extension method to System.Object, except the method expects the class it's called from to have the properties Name and KeyData, which System.Object doesn't include. I suppose I could write some unreadable reflection code to extract the class's TypeName, but really, the programmer who creates the class should decide what counts as "KeyData" for the class.
Leveraging Interfaces
This is where interfaces come in to save the day. Like inheritance, interfaces are a way to make different classes "look alike." I can, for instance, define an interface called ILogInfo that specifies the properties that my LogInfo method requires:
Public Interface ILogInfo
Property Name As String
Property KeyData As String
End Interface
Any class, in addition to whatever objects it inherits from (or whatever other interfaces it already implements), can implement my ILogInfo interface. Here's a Visual Basic class that inherits from an object called CustomerBase and also implements both the IEnumerable and my ILogInfo interfaces (I've also included the properties required by ILogInfo):
Public Class Customer
Inherits CustomerBase
Implements IEnumerable
Implements ILogInfo
Public ReadOnly Property Name() As String Implements ILogInfo.Name
Return "Customer"
End Property
Public ReadOnly Property KeyData() As String Implements ILogInfo.KeyData
Return Me.ID & ", " & Me.Status
End Property
Of course, when developers add my ILogInfo interface to a class, they'll be obliged to provide code for the methods and properties specified in the interface (Name and KeyData, in this example). That's actually a good thing: Each class that implements ILogInfo should provide what counts as KeyData for that class.
With this interface in place, I can rewrite my LogInfo method as an extension method that attaches itself to any class that implements ILogInfo:
Public Module PHVExtensions
<System.Runtime.CompilerServices.Extension()>
Public Sub LogInfo(ByVal logSubject As ILogInfo)
Console.WriteLine(logSubject.Name & ": " logSubject.KeyData)
End Sub
End Module
Going forward, for a class that I want to log information about, I now have to do two things: First, have that class implement ILogInfo; second, add to the Name and KeyData methods whatever code I think the class requires. As soon as I've done those two things I will find my LogInfo method appearing in the IntelliSense list for that class. It's almost magic. It's certainly cool.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.