Extension Methods Best Practices (Extension Methods Part 6)
This is the sixth installment in my series of posts about extension methods. You can find links to the rest of the series here.
Today I am going to talk about some best practices for using extension methods. Most of this content is geared toward authors of class libraries designed for wide spread consumption. Developers of such libraries often need to worry about things like making their API’s future proof, and finding ways to extend their libraries while still preserving backwards compatibility. In most real world applications these suggestions can (and quite frankly should!) be completely ignored. However, if you are in the business of creating components or libraries used by many developers, than heeding this advice should help to ensure that your consumers, particularly those using VB, have a pleasant experience with your API.
1. Read the .NET Framework Class Library Design Guidelines
The .NET Framework Class Library Design Guidelines, available here, provide guidance on how to write highly usable, secure, and extensible class libraries for the .NET Framework. Also, Brad Abrams and Krzysztof Cwalina wrote a book on how to do this. You should check them out.
2. Be wary of extension methods
For an application programmer, extension methods are an incredibly powerful and expressive tool. They enable convenience, extensibility, and an improved intellisence experience. However, many of the features that make extension methods so useful for library consumers can be problematic for class library authors. In particular, there are 2 primary issues I outline below that you should be aware of when using extension methods in class libraries, particularly for mass-market libraries that need to maintain backwards compatibility across multiple releases.
The first issue is that extension methods have “hide-by-name” semantics with instance members on a type. This means that any accessible instance member on a type will always shadow any extension methods with the same name, even if the extension method is a better fit (see part 4 for details). As a result, if an instance member is ever added to a type with the same name as an extension method, then the extension method can be rendered uncallable. This can make extension method libraries somewhat fragile.
The second issue is that an extension method author cannot prevent other programmers from writing conflicting extensions. Instance method authors can mark their classes as “notinheritable” and prevent them from being augmented by derived classes. Similarly, methods marked as “overridable” in abstract base classes can be marked as “notoverridable” when they are overridden in a derived class, disallowing them from being overridden by classes further down in the hierarchy. Instance methods cannot be overridden by extension methods. These things give class library authors certain degrees of certainty about which instance methods will be called in which situations. Unfortunately, however, extension method authors have no such guarantees. If a library user brings a third party extension method with the same name as yours into scope then that method may be preferred over yours. As a library author there is nothing you can do to prevent this.
As a result, you should be cautious when adding extension methods to your class libraries unless either:
- These risks are unimportant (as is the case with most internal libraries), or
- You take steps to mitigate them as outlined in the rest of this article
3. Put extension methods into their own namespace
One simple way to improve the robustness of your extension method library is to put extension methods into their own namespace. This is the strategy employed by the 3.5 version of the .NET Framework. By putting extension methods into their own namespace you enable consumers to include or exclude them separately from the rest of your library. This makes them pluggable, allowing users to easily replace extension method implementations with other implementations if they wish (which is at the heart of the LINQ provider model). This in turn then makes it easier for them to resolve any conflicts that may arise (see part 2 for information about extension method precedence levels).
4. Think twice before extending types you don’t own
If you only write extension methods for types you own, then you never have to worry about your extensions being broken by changes to the type being extended. On the other hand, if you are extending other peoples types than you are essentially at their mercy. Alternatively, you can minimize this risk by choosing names that are unlikely to be used by someone else, or by minimizing the total number of extension methods you define (i.e reducing surface area). One technique for doing this may be to write one extension method that converts the underlying object to a different type that is controlled by you.
5. Prefer interface extensions over class extensions
This may seem like odd advice, given that the The .NET Framework Class Library Design Guidelines referenced above state that you should “prefer classes to interfaces”. However, the reasons for advocating interface extensions over class extensions are identical to the reasons for advocating the use of classes over the user of interfaces in that document: interfaces cannot be safely versioned. In other words, an interface cannot be changed without requiring every class that implements it to be modified. This means that, for the most part, as a class library author you can treat interfaces as immutable types (if they do change, all their clients will be broken anyways, so you can’t make the problem any worse). Classes, on the other hand, can add new methods without breaking their clients, which is why the design guidelines recommend them (at least they could prior to the introduction of extension methods, and so to be safe you should assume that other class library authors may continue to follow that convention). As a result, interface extensions are inherently less fragile than class extensions.
This isn’t perfect, as interface extensions can be visible from instances of implementing classes, which may then change in future versions by adding new methods. However, these issues can always be worked around by casting any such classes to instances of the interface. With class extensions, such a workaround may not be possible.
6. Be as specific with the types you extend as possible
Extensions on less specific types are more likely to be broken by external change than extensions on more specific types. This is because the higher a type is in the hierarchy, the more types there are that derive from it. An extension method on type “Object” can be broken by the introduction of any member to any type anywhere. An extension method on “string”, on the other hand, can only be broken by changes to string or other extension methods. Therefore, by preferring more specific types, you can reduce the likely hood of other people’s changes impacting your API. This is a little at odds with item #5, as interfaces are by definition less specific than classes. As a library author you would have to find the right balance between them.
7. Please ignore this advice where it is not appropriate
I mentioned this at the beginning of the post, but I thought it was worth pointing out again: for most programs you should ignore this advice. Adding extension methods to any type is a great way to improve productivity and simplify code. You should do this wherever it feels beneficial to you, without worrying about any of these details. However, if you need to write an API that can be consumed by a lot of programmers (say thousands or even millions), then I think it’s worthwhile for you to consider these constraints in your designs.
That’s all I have for today. Stay tuned for my next post where I will break away from extension methods and start to talk about some of the other cool features we are introducing into the language.