Extension methods in C# provide a mechanism for adding functionality to a class when you do not control the source code to that class. Most often, extension methods are used as a means of syntactically treating external utility methods as though they were methods on the class itself. In this use, the main difference between extension methods and instance methods is that extension methods do not have access to the non-public state of the object.
There has been some discussion on various blogs and forums about another use of possible extension methods. This use suggests that extension methods can be used to implement Mixins and other constructs that require a language to support multiple inheritance.
The discussion has included some debate about the value of using this approach vs. composition which has served as the traditional mechanism for taking advantage of multiple inheritance’s benefits in a single inheritance language.
I recently came across a case where I decided to utilize the extension method approach. The scenario was a refactoring of an existing codebase that included a well-entrenched object model. I needed to extract an interface and the related implementation from an existing class and then leverage both in several new classes. The class from which the interface was extracted already inherited from a base class. In an attempt to keep the refactoring as simple as possible, I decided not to change this inheritance relationship. Since I did not want to change the base class of this existing class and it did not make sense for the new classes to derive from that base class, I needed another approach for giving them a common interface and implementation.
The approach I chose for this was to extract the interface and then create a set of extension methods that extend the extracted interface. By adding this interface to the existing and new classes, I could treat them all as the same type. By utilizing extension methods, I was able to add methods to these classes that shared the same implementation. Note that in this approach, the extension methods are not part of the interface.
The alternative approach would have been to utilize composition. This would have involved including all of the common methods as part of the interface, creating a class that defines the shared implementation of these methods, and delegating to an instance of this class as the implementation of each of these methods.
Let’s examine briefly the benefits and costs of each of approaches.
Extension Method Approach
The main benefit of the extension method approach is that I did not have to repeat the same code over and over in each of the classes to delegate to the class that provided the implementation of the interface methods. This saved me from writing a decent amount of code. The related benefit is that I can add additional methods to all of the classes that implement the interface simply by adding new extension methods. The interface itself and the classes do not need to change at all.
One downside to using this approach is that I need to import the namespace that contains the class that implements the extension methods into all code files that will make use of the extension methods. This would have been a headache except for the fact that the extension methods are defined within the same namespace as the interface. Because of this, I got to utilize the extension methods without any additional work.
The other major downside is that I am somewhat locked into a single implementation of each extension method for all class that share the interface.
The main benefit to using the composition approach is that I would have the freedom to provide alternative implementations for the interface methods either by coding directly within the classes that implement the interface, by delegating to a different class, or by delegating to a different method on the same class. This would provide me with a great deal more flexibility down the road.
I think the only real downside to this approach is the added coding effort that I mentioned above.
In summary, the composition approach appears to be the better of the two if you are looking for the most flexible option and anticipate a need to provide alternative implementations for different classes. On the other hand, the extension method approach can be a viable alternative if you anticipate that the implementation of the methods will remain the same for all classes that share the interface and you value the benefit of writing less code (i.e., you have a large number of classes sharing the interface).
Of course, it is possible to start with the extension method approach and then refactor to use the composition approach at a later date if it becomes desirable to do so.
Ideally, you could avoid the need to use either of these approaches by identifying upfront a design that would allow this shared functionality to be placed in a common base class. However, it is not always possible to anticipate this type of requirement as your system may evolve over time to include unanticipated uses. Also, there are likely to be many scenarios in which classes that require shared functionality should not share a common base class. In such cases, composition would appear to be the better alternative due to the flexibility that it provides in making future changes. In cases where you are refactoring to add an interface to a large number of classes that cannot share a common base class, the extension method approach can be a viable alternative that leaves room to switch to composition later if needed.