11/27/07

Extension methods: one of my favorite C# 3.0 features

With C# 2.0 I sometimes had a feeling that something bigger is coming. As it now turns out, it was C# 3.0 (surprise!). Now the whole picture makes perfect sense, all the features start playing together seamlessly and I have to say that it is an awesome release. LINQ is a fundamental breakthrough and all the machinery that was required to implement it is great as well. I especially like extension methods, because they allow me to write more concise and expressive code than earlier.


Enhancing existing APIs


Before C# 3.0, the creator of an API or a framework was solely responsible for the readability and the usability of the API. Users of the API had to consume available methods as they were, unless they were ready to build own facades, adapters or decorators. Now the clients of an existing API can significantly enhance its readability, expressiveness and usability without even changing a single bit about it.


Addind default implementation to interfaces


Another advantage is similar to type classes in Haskell - ability to add some default implementation to existing interfaces. The best example for this is IEnumerable<T>, which now boasts so much more functionality than earlier! And note: we haven't changed a thing about IEnumerable<T>, we just added some stuff somewhere else, and all IEnumerable<T> implementations suddenly became so much more powerful. As we know from my previous posts, this is like adding 'mixins' to the language - just implement the interface, and you get this default functionality for free.


Composability and fluent interface


Also, an important advantage of extension methods is that they can be called on null objects without any NullReferenceExceptions. The best example for this is of course the string.IsEmpty() method:


        public static bool IsEmpty(this string possiblyNullString)
{
return string.IsNullOrEmpty(possiblyNullString);
}


This simply boosts composability because we don't have to check for null before actually calling a method on an object - we can do it inside the extension method. Earlier, callers were responsible to check for a null object every darn time they wanted to call an instance method on that object. Now, if we can gracefully handle a null object without throwing (e.g. just return null), it saves so much effort on the caller's side. As I said, our calls become truly composable, for example, I can chain calls to Object1.Method2().GetObject3().TryAndGetObject4().OhAndPerhapsTryGettingObject5AsWell() without worrying that one of the methods might return null. If it does return null, the whole chain won't throw and will just return null. Of course, there can be a lot of pain debugging this thing because we don't save intermediate results, but this is still a huge enabler for People Who Know What They Are Doing. This composability is a also a nice helpful feature for designing Fluent API (speaking of fluent API... what about extension properties??)



Extension methods in action: populating and grouping TreeView items



Now let me show a couple of delicious examples. I had an interesting problem recently. There was a list of objects (say Cars), each of the objects had four properties. Let's say like this:


    class Car
{
public string Make { get; set; }
public string Model { get; set; }
public int Year { get; set; }
public Color Color { get; set; }
}

Now, I had to add all Cars from the list to a WPF TreeView control, and hierarchically group cars by Make (similar makes go under a single node), then by Model, then by Year, and finally by Color. This is actually a pretty good interview question, and as I usually totally suck at interview questions, I started building an overly complicated strongly typed object model to represent a MakeGroup (based on Dictionary), which contains a ModelGroup, which contains a YearGroup etc. etc. I would then use LINQ to query over the master car list, group by make, for each group create a MakeGroup object, etc. etc. After building the object model was done, I would walk it with a special TreeViewItemBuilder that would build the treeview items and nest them correspondingly. After about 1,5 hours of this mess (unfortunately, interviews usually last less than 1,5 hours) I got struck by a what I think is an excellent idea. I wrote this extension method:


    public static class WPFExtensions
{
public static TreeViewItem FindOrAdd(
this ItemsControl parent,
string header)
{
// try to find an existing item with this name
TreeViewItem result =
parent.Items.Cast<TreeViewItem>()
.FirstOrDefault(x => x.Header.ToString() == header);

// if not yet there, don't throw, just create it in place
if (result == null)
{
result = new TreeViewItem { Header = header };
parent.Items.Add(result);
}

// and return
return result;
}
}

Now, I threw away my overly engineered "solution" and came up with this instead:


            foreach (Car car in GetCars())
{
treeView1
.FindOrAdd(car.Make)
.FindOrAdd(car.Model)
.FindOrAdd(car.Year.ToString())
.Items.Add(new TreeViewItem { Header = car.ToString() });
}

These are 8 lines instead of 200+ I wrote first. This is much more manageable and flexible than my original solution - you can easily change the code to e.g. first group by Model, then by Name - by swapping two lines of code. If you're interested in the full source code of this sample, just leave a comment and I'll post it here (that's what I call lazy sample upload - long live the functional approach!). Beside extension methods, this sample also demonstrates: auto-implemented properties, object initializers and lambda expressions! Try and spot them all!


More examples


I'll go ahead and post more examples of extension methods and how they help me write cleaner code everyday.



1. Collections


It's nice to be able to check if a collection doesn't contain items in a single step:


        public static bool IsEmpty<T>(this ICollection<T> possiblyNullCollection)
{
return possiblyNullCollection == null || possiblyNullCollection.Count == 0;
}

ICollection<T> lacks AddRange:


        public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
{
foreach (var item in items)
{
collection.Add(item);
}
}

Or adding several items separated by commas:


        public static void Add<T>(this ICollection<T> collection, params T[] items)
{
foreach (var item in items)
{
collection.Add(item);
}
}

It's nice to be able to call a given method for each element:


        public static void ForEach<T>(this IEnumerable<T> collection, Action<T> action)
{
foreach (T item in collection)
{
action(item);
}
}

Or have a composable GetValue on Dictionary that doesn't throw:


        public static V GetValue<K, V>(this IDictionary<K, V> dictionary, K key)
{
V result;
if (dictionary.TryGetValue(key, out result))
{
return result;
}
return default(V);
}

2. Strings


For strings, everyone already seems to have developed their own little library. So I think I won't be original if I share these:


        public static bool IsEmpty(this string possiblyNullString)
{
return string.IsNullOrEmpty(possiblyNullString);
}

public static void Print(this string str)
{
Console.WriteLine(str);
}

public static void MessageBox(this string str)
{
global::System.Windows.MessageBox.Show(str);
}

I also found it to be very convenient to have the following (trivial implementation, doesn't throw):



        public static int? ToInt(this string textNumber)
public static bool? ToBool(this string boolValue)

These guys rock because they easily compose and don't distract me with the necessity to call TryParse or such. BTW I think that the composability of any TryParse method is not very good, in case you haven't noticed.


3. XML


Last but not least, a nice enhancement to XElement:


        public static string GetElementValue(this XElement element, string elementPath)
{
if (elementPath.IsEmpty())
{
return element.Value;
}

XElement subElement = element.Element(elementPath);
if (subElement == null)
{
return null;
}

return subElement.Value;
}


It doesn't throw, which saves me a lot of effort every time I want to call it - I don't have to be afraid that Element() can return null.


Conclusion


Of course, there are caveats. Many people critisize extension methods for the lack of discoverability. Indeed, extension methods for a type only appear in the Visual Studio IntelliSense if you add a using declaration with a namespace where the extension methods are declared. This is currently a discoverability problem.


Also, others mention that it is difficult to read existing code with extension methods because we don't know the meaning of methods and where they are declared. My answer - use F12 (Go to Definition) in Visual Studio.


Finally, there is a versioning problem.


But still, I think extension methods are so cool and they provide so many advantages, that it is well worth using them anyway. Especially if you belong to those people who Know What They Are Doing.



kick it on DotNetKicks.com

9 comments:

Anonymous said...

I don't think that extension method for treeView is obligatory to implement this classifier for car.
Following code (written in 'old' style) has some additional lines but hasn't problem with versioning like extension methods (sorry, for my java language, unfortunately has no C# nearly =)).

/
// Instead of extension method
public class TreeViewClassifier {

private final HashMap treeViewItemsToClassifierItems = new HashMap();

private final TreeView treeView;

/**
* @param treeView
*/
public TreeViewClassifier(TreeView treeView) {
this.treeView = treeView;
}

public Items FindOrAdd( String header ) {
return Wrap( this.treeView.Items).FindOrAdd(header);
}

private Items Wrap( TreeViewItemCollection rawItems ) {
Items items = this.treeViewItemsToClassifierItems[ rawItems ];
if ( items == null ) {
items = new Items( rawItems );
}
return items;
}

public class Items {

private final TreeViewItemCollection items;

/**
* @param items
*/
public Items(TreeViewItemCollection items) {
super();
this.items = items;
}

public Items FindOrAdd( String header ) {
TreeViewItem itemByHeader = this.items.ImplementationOfFindItemByHeader( header );
if ( itemByHeader == null ) {
itemByHeader = new TreeViewItem( header );
}
return Wrap( itemByHeader.getItems() );
}

public void Add( TreeViewItem item ) {
this.items.Add( item );
}

}


}

// In action
TreeViewClassifier classifier = new TreeViewClassifier( treeView );
for( Car car : cars ) {
classifier.FindOrAdd( car.Make )
.FindOrAdd( car.Make )
.FindOrAdd( car.Year.toString() )
.add( new TreeViewItem( car.toString() ) );
}

Kirill Osenkov said...

Hi Dmitry,

you're right, extension methods are not necessary to implement this. The usability of your method looks almost exactly like mine but doesn't use extension methods and doesn't have their drawbacks.

Good stuff!
Kirill

Anonymous said...

Kirill,

Just came across your blog. Good stuff. On the extension methods, I have added the following to my library. Lambda everywhere!. Also have similar methods for IsNull, HasValue & IsEmpty.

-Andy

>>

public static bool IsNullOrEmpty(this string value)
{
return string.IsNullOrEmpty(value);
}

public static bool IsNullOrEmpty(this string value, Action< string > action)
{
if (value.IsNullOrEmpty())
{
action(value);
return true;
}

return false;
}

Kirill Osenkov said...

Interesting! Thanks Andrew!

Anonymous said...

Good sample of usage for such remarkable language feature as Extension methods.

In our application when the same problem appears we use a bit different variant of solution without extension method:

static IEnumerable<string> GetProperties(Car car)
{
yield return car.Make;
yield return car.Model;
yield return car.Year.ToString();
}

private void InitializeTree()
{
ItemsControl root = treeView1;

foreach (Car car in GetCars())
{
ItemsControl current = root;
foreach (string propertyName in GetProperties(car))
{
ItemsControl child =
current.Items.Cast<TreeViewItem>().FirstOrDefault(i => i.Header == propertyName);
if (child == null)
{
child = new TreeViewItem { Header = propertyName };
current.Items.Add(child);
}
current = child;
}
current.Items.Add(new TreeViewItem { Header = car.ToString() });
}
}

Kirill Osenkov said...

Great stuff desco!

I like that you use IEnumerable to return the "path" in the tree, this is more flexible than having a fixed-depth tree of depth 3.

However I would still recommend extracting the logic into a separate FindOrAdd method. It doesn't necessarily have to be an extension method. This will promote reusing the code and allow you to be more explicit about your intention: for the current node, either find an existing node with a given header, or create a new one if it doesn't exist.

Anonymous said...

The code I've posted is just a short demonstration of my version adapted to your model. In production find/add logic was extracted into separate method

Kirill Osenkov said...

That's great. Thanks!

Anonymous said...

Nice article on Extension methods...If possible can you post the source code link for the Treeview