Thursday, December 6, 2007

Extension Powers for Good instead of Evil

There's been quite a bit of discussions lately on a variety of mailing lists that I subscribe to about the power behind the Extension Methods functionality that is present within the C# 3.0 and VB.NET 9.0 compilers.

While I definitely agree that Extension Methods could potentially be abused, they may also provide a good service as well, which I will describe with an example.

I use the Castle Project components, for instance Windsor and ActiveRecord.  However, to keep functionality separate and distinct, instead of inheriting from ActiveRecordBase<> and allowing everyone able to see the domain model access to use the data services, instead I implement a repository service.  Besides limiting the functionality of accessing the repository only to the tiers of the application that need the access, this also allows me to not require the Castle.ActiveRecord dependency throughout the program, and I can abstract the concrete class and limit the exposure of Castle.ActiveRecord to only one assembly.

However, this technique also leaves some horrid syntax compared to the ActiveRecordBase<> implementation:

Product[] items = Product.FindAll();

versus

Product[] items = container.GetService<IRepository<Product>>().FindAll();

And it gets worse if you do anything fancy, like for example I like to wrap transactions within my repository:

using IRepository<Product> repo = container.GetService<IRepository<Product>>();

I also like (whether it's proper or not is left to the reader), to create a "core" of my application for the logic layers, and with a base static class, such as "App".  This App class houses my windsor container (allowing me to contain the exposure of Castle.Windsor and Castle.MicroKernel as well).  Within this class I separate functionality into sub properties, such as a Model property of type "ModelServices" for model services:

App.Model.NewRepository<Product>();

which changes the code above:

Product[] items = App.Model.NewRepository<Product()>().FindAll();

While this is a little cleaner, in my opinion anyway, it still is very involved when it comes to reading code.

Extension methods can actually help here, at least to some extent.  As an example, if I were to create a new extension method, that is in the same namespace as the Product class, such as:

public static class ProductExtensions

{

public static IRepository<Product> Product(this ModelServices target)

{

return target.NewRepository<Product>();

}

Now with this extension, any code which pulls from the same namespace as where the Product class is can clean up a little bit:

Product[] items = App.Model.Product().FindAll();

I like this because it allows me to bring in new pieces into my services in a cleaner manner than having to request GetService, while at the same time keeping things separated and loosely coupled.

Any comments or feedback on this tactic would be appreciated :).

2 comments:

arzewski said...

Just because YOU CAN doesn't mean it is necessarily a good thing to do. Extending a class's API with additional signatures means diverting the original designer's intent with your own custom design, additionally polluting the namespace. Case in point: the DateTime class. It is well known to all that it doesn't have much information of month names. That's because it is found in GregorianCalendar, when the culture info is United States and using the English language. So, getting today's month name is a bit cumbersome, having to use many separate classes just to get that info. Ever tried populating a dropdown listbox with english month names, January to December? not so obvious... So, the developer might be tempted on extending the DateTime class with MonthNameArray that returns string[] (an array of strings). Fine. That was in one departmental project. In another departmental project, they might be doing their own custom extensions to the DateTime class. Then, developers migrate from one project to another, and bring with them their own personal extensions, and add them again to the new project's DateTime class. OK, now, pressing the dot shows IntelliSense retrieving HUNDREDS of new methods, some may be named PrintMMDDYYYY, PrintLongMonthDayLongYear, etc etc. Just imagine when a manager wants to merge these two apps. You might just have two separate methods, similarly named, that might just do almost the same thing. Or, what about when you "reuse" some code, but this code is invoking methodds that don't exist in your DateTime class... Guess what: you will be forced to extend your DateTime class with their extensions. This is a new feature that is being allowed by the compiler. People think it is cool. Great. Now the gates are open. Before going gung-ho on these extensions, consult with other developers that have used them in other programming environments, and ask them about the pros and cons, specially after a couple of years on a project. You will see there is a price in future maintenability. My advice: add extensions to a class only when there is no other way to do what you want to do. If there is a different pattern or helper class that achieves the same goal, go for that solution. If you inherited a DLL-ized component for which you do not have the source code, and the API framework that you are using requires that this DLL-ized component, in order to be usable, needs to understand the message being sent to it named "DisplayValueName" and it takes a signature of "string, int", then, go ahead, and extend that DLL-ized component with public void DisplayValueName (string param1, string int). And yes, not allowing properties to be extended a well is an annoying disadvantage. Example: you have a listbox whose DisplayMember is a property named "DisplayValue". You might have two types of objects in that listbox, one from a class you have the source code for, and another that you don't, and it does not allow subclassing. Unfortunately, there is no way to extend the second class with the property "DisplayValue", and the listbox will not be displaying those objects correctly.

Just because some developers in love with Ruby or other dynamic language now want to implement synthatic constructs that mimic some of the code snippets they were able to do in Ruby, means that they now must change all the synthax and code constructs and order of message invokation. Like Bjarn Stoustrup said to an overzealous developer that wanted to implement Smalltalk-like and LISP-like dynamic language frameworks into C++, "why would you want to do that in C++ ? Smalltalk is the best Smalltalk around".

Rick Fleming said...

I definately agree that Extension Methods can be majorly misused, but I would rather have a featureful development language and hold restraint then a language which tries to limit your capability for "safety" purposes. I cringe at the thought of all the COM-VB hacks that had to be done to do something that was simple enough in C.

In this case, the idea is to dynamically allow methods to be used dependant upon what is available through your project references and using namespace clauses. I for one exhibit serious control over project and component references, as well as code refactoring in general. I would definately agree with you though, they can be misused, although because of my own practices I won't avoid them as much as other people seem to want to.

It doesn't matter what the language feature is or what the language is, I've seen many, many, projects where things were used in a horrific manner, doesn't mean I won't use those features in the right context. And I made a good living cleaning some of those messes up.


I expanded this Repository set into a more complete set that I might release at some point.

Thanks for the comment!