Extending the Behavior of an Interface Using Contravariance, Generic Typing and Extension Methods

A Review of Variance

A fundamental concept in Object-Oriented Programming is the notion of variance. Variance is commonly observed in languages that allow subtyping (i.e. an object can be derived from a higher-level object by inheriting its ancestor's properties). It is often invoked as a technique to develop code that is highly re-usable, both within and across multiple projects. Variance is achieved when one object is either cast as a different type or instantiated by a class that implements another class. The term covariance refers to instances where an object is derived from a base type or class, thereby inheriting some or all of its attributes. Contravariance on the other hand implies the opposite, where an object of a more derived type is converted back to a higher-level form.

In the quest to develop code that is ever more re-usable and concise, many developers have become accustomed to maintaining their own libraries of utility classes (classes used to perform a routine function) and extension methods (utility methods that can be applied to entire types or classes of objects in a manner that makes them appear as though they are an inherent method of that object). With the introduction of C# 3.0, extension methods have become a popular means of extending feature-rich behavior to a large variety of objects with minimal effort. In doing so, extension methods are commonly attached to primitive types or classes to achieve certain behavior for any instance of that type or class. But what if you do not know in advance the types of the objects that will require the functionality defined by the extension methods?

In this article, I illustrate how to take this approach a step further by using contravariance and generic typing to extend a public interface. When used in this manner, extension methods can be exposed on any object that implements the interface. One widely used interface in the C# language, IEnumerable, allows for the creation of collections that the caller can iterate over. The following section demonstrates how to extend the behavior of IEnumerable to provide custom processing for any enumerable collection.

Creating the Extension Method

We begin by creating a static class called “MyExtensions” that will serve as a wrapper for our extension methods (extension methods must be defined as static methods within a static class). Our first method, ProcessCollection<T>, is attached to the IEnumerable<T> interface by applying the “this” keyword to the first parameter. Note that by using generic typing (as indicated by <T>), we ensure we can use this method on any enumerable collection, regardless of the type of its elements (i.e. List<int>, List<string>, Object[], etc.). The purpose of our second parameter is to provide a way for the caller to specify which custom operation to invoke. The main processing component of the method then begins at the case statement, where an evaluation is performed to determine which custom operation to execute.











Calling the Extension Method

Once we have set up our static class and extension method, we are ready to call it on any object implementing IEnumerable<T>.



If we were to output the contents of each collection above, we would obtain the following results:



Why would you apply an Extension Method to an Interface?

As alluded to earlier, there are situations where one might want to expose a wide range of custom behavior to a variety of objects but the exact types of those objects are unknown at compile time. The development of a public API, for instance, might be an example. In such circumstances, extending an interface affords great flexibility by allowing any object implementing that interface to inherit its extended behavior. Moreover, through the use of generic typing, we are able to attain further benefits by introducing the ability to accommodate any declared type supported by the interface.

Conclusion

In this article, we have seen one approach to developing re-useable C# code that provides customized behavior through the use of contravaraince, extension methods and generic typing. My intent is that the information provided will be used as a starting point for other developers wishing to explore similar design solutions for their development projects. As with any feature of the C# language, careful analysis should be given when determining if the methodology above represents the best option for a particular situation.