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) endendputs "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?).
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:
Category
ArrayList
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) endendputs "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?).
first
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 + "/", "/"); } }
{
builder
}
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)
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.
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