I’ve finally broken down and used the Extension methods feature in C# 3.0. The feature is similar to the concept of “open classes” in Ruby described in Neal Ford’s post Are Open Classes Evil? which contains the following excerpt

Open classes in dynamic languages allow you to crack open a class and add your own methods to it. In Groovy, it's done with either a Category or the Expando Meta-class (which I think is a great name). JRuby allows you to do this to Java classes as well. For example, you can add methods to the ArrayList class thusly:

require "java"
include_class "java.util.ArrayList"
list = ArrayList.new
%w(Red Green Blue).each { |color| list.add(color) }

# Add "first" method to proxy of Java ArrayList class.
class ArrayList
def first
size == 0 ? nil : get(0)
end
end
puts "first item is #{list.first}"


Here, I just crack open the ArrayList class and add a first method (which probably should have been there anyway, no?).

In my case, I added a CanonicalizedUri() method the System.Uri class because I was tired of how we had to have all sorts of special case code to deal with canonicalizing URIs because Uri.AbsoluteUri property would not represent http://www.example.com and http://www.example.com/ as the same URI and a couple of other special cases.  My extension method is shown below

/// <summary>

/// Helper class used to add extension method to the System.Uri class.

/// </summary>

public static class UriHelper

{

/// <summary>

/// Returns a the URI canonicalized in the following way. (1) if the file is a UNC or file URI then it only returns the local part.

/// (2) for Web URIs it removes trailing slashes and preceding "www."

/// </summary>

/// <param name="uri">The URI to canonicalize</param>

/// <returns>The canonicalized URI as a string</returns>

public static string CanonicalizedUri(this Uri uri)

{

if (uri.IsFile || uri.IsUnc)

return uri.LocalPath;

UriBuilder builder = new UriBuilder(uri);

builder.Host = (builder.Host.ToLower().StartsWith("www.") ? builder.Host.Substring(4) : builder.Host);

builder.Path = (builder.Path.EndsWith("/") ? builder.Path.Substring(0, builder.Path.Length - 1) : builder.Path);

return builder.ToString().Replace(":" + builder.Port + "/", "/");

}

}

Now everywhere we used to have special case code for canonicalizing a URI, we just replace that with a call to uri.CanonicalizedUri(). It’s intoxicating how liberating it feels to be able to “fix” classes in this way.  

I have seen some complain that coupling this feature with Intellisense (i.e. method name autocomplete) leads to an overwhelming experience. Compare the following screenshots from Jessica Folser’s post using System.Linq, sometimes more isn't better which shows the difference between hitting ‘.’ on an Array with the System.Linq namespace included versus not. Note that the System.Linq namespace defines a number of extension methods for the Array class and its base classes.

WITHOUT SYSTEM.LINQ (20 methods)
Without System.Linq, Array has 20 methods

USING SYSTEM.LINQ (68 methods)

Using System.Linq, Array has 68 methods

I did find this disconcerting at first but I got used to it.

It should be noted that “open classes” in Ruby come with a bunch more features than extension methods in C# 3.0. For example, Ruby has remove_method and undef_method which allows developers to remove methods from a class. This is particularly useful if there is a particularly buggy or insecure method you’d rather was not used by developers in your project. Much better than simply relying on the Obsolete attribute. Smile

One problem I had with C# is that I can’t create an extension property only methods (so my CanonicalizedUri() had to be a method not a property like Uri.AbsoluteUri).  I assume this is due to difficulty in coming up with a backwards compatible extension to the syntax for properties. Either way, it sucks. You can count me as another developer who is voting for extension properties in C# 4.0 (or whatever comes next).

Now playing: Oomp Camp - Intoxicated


 

Wednesday, January 23, 2008 8:03:59 AM (GMT Standard Time, UTC+00:00)
I'm interested to know why you chose the extension method instead of having a CanonicalizedUri static method in UriHelper that takes a Uri as an argument.
Wednesday, January 23, 2008 12:00:51 PM (GMT Standard Time, UTC+00:00)
One thing that'll make it easier to see which methods are extension methods when skimming a code file will be ReSharper 4.0. They've said that they'll add the option to color extension methods differently, which should a big help.
Oren Novotny
Wednesday, January 23, 2008 2:50:55 PM (GMT Standard Time, UTC+00:00)
Dan V,
Because an extension method is clearer and more Object Oriented than passing around Uri instances to helper classes. I much prefer

list.Add(uri.CanonicalizedUri())

to writing

list.Add(UriHelper.CanonicalizeUri(uri))
Thursday, January 24, 2008 3:36:17 PM (GMT Standard Time, UTC+00:00)
Seeing them next to each other makes the readability advantage clear, thanks.
Friday, January 25, 2008 1:22:39 AM (GMT Standard Time, UTC+00:00)
I think the fact that you can't see the difference between built in methods and extension methods is an intellisense issue.

+1 on Extention properties

As far as method use restriction goes, I'd love for FxCop to give us a simple way of writing parameterized rules so I could just use that. I'm imagining an FxCop rule that searches for something in my project file that VS could also pick up on and use to filter intellisense.

-hoop
Hoop Somuah
Comments are closed.