Wednesday, December 06, 2006 1:24 PM bart

C# 3.0 Feature Focus - Part 4 - Extension Methods

 

Introduction

In this C# 3.0 Feature Focus Week we'll focus on the new language features that will be part of C# 3.0 "Orcas". As you already might know, the major innovation on Orcas is the LINQ (Language Integrated Query) technology that will make data queries a first class citizen of the language (C# 3.0 and VB 9.0). In order to unlock this potential, a set of language features were added to the language that all work in concert to enable LINQ stuff. However, all of those individual features prove to be useful in isolation as well. Later, we'll focus on LINQ itself.

 

Extension Methods

To set our minds, assume the following scenario: you're doing a lot of string manipulations and one of the operations you need quite often is reversing a string. Since strings are immutable, a new string has to be created with the reversed set of characters in it. A typical solution looks like this:

using System;

class ReverseStringDemo
{
   public static void Main()
   {
      string s = "Hello World!";
      string r = Helpers.Reverse(s);
   }
}

static class Helpers
{
   public static string Reverse(string s)
   {
      char[] c = s.ToCharArray();
      Array.Reverse(c);
      return new string(c);
   }
}

Although this approach works like a charm, it has a few drawbacks. First of all, the code is less intuitive because of the need to call a static method which happens to be defined in "some" static class. (Note: Static classes were introduced in C# 2.0 as a way to enforce - at compile time - all member of a class to be static. This feature was the direct result of an API issue in .NET Framework 1.1 where an instance member was added to the System.Environment class which didn't have a public constructor, so the newly added instance member was useless. As the matter in fact, the member had to be static but the error was not caught at compile time. Static classes avoid this kind of problem to arise.) Another drawback is the disruptive nature when results have to be chained. Assume the following:

      string s = "Hello World!";
      string t = s.TrimLeft().ToLower();
      string r = Helpers.Reverse(t).Substring(0, 15).PadRight(20, '*');

It would be much nicer to be able to write everything in one chain of calls, like this:

      string s = "Hello World!";
      string r = s.TrimLeft().ToLower().Reverse().Substring(0, 15).PadRight(20, '*');

This becomes even more convenient when a lot of "extensions" would have to be added, for example to every possible collection type which is exactly what LINQ needs to do: take a collection, filter items based on a predicate, sort it, group the results, project, etc. Basically, this is a chain of calls that transforms one collection into another, like this:

      var result = customers.Where(c => c.Age >= 18).Select(new { c.Name, c.Age });

Extension methods allow us to realize this. Basically, you can turn static methods on a static class into extension methods by just adding the this keyword to the first method parameter:

using System;

class ReverseStringDemo
{
   public static void Main()
   {
      string s = "Hello World!";
      string r = s.Reverse();
   }
}

static class Helpers
{
   public static string Reverse(this string s)
   {
      char[] c = s.ToCharArray();
      Array.Reverse(c);
      return new string(c);
   }
}

Note: The extension method can still be used as a regular static method too, so you can turn existing static methods (on static classes) into extension methods without breaking compatibility.

The Main method in the code fragment above is turned into the following IL code:

.method public hidebysig static void Main() cil managed
{
   .entrypoint
   .locals init (string V_0,
            string V_1)
   IL_0000: nop
   IL_0001: ldstr "Hello World!"
   IL_0006: stloc.0
   IL_0007: ldloc.0
   IL_0008: call string Helpers::Reverse(string)
   IL_000d: stloc.1
   IL_000e: ret
} // end of method ReverseStringDemo::Main

As you can see, extension methods are just some syntactical sugar to masquerade static methods calls, in order to make source code cleaner and more intuitive.

The Reverse method is translated in a regular static method but annotated with an attribute that makes it recognizable by the compiler as an extension method:

.method public hidebysig static string Reverse(string s) cil managed
{
   .custom instance void System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
   .locals init (char[] V_0,
            string V_1)
   IL_0000: nop
   IL_0001: ldarg.0
   IL_0002: callvirt instance char[] [mscorlib]System.String::ToCharArray()
   IL_0007: stloc.0
   IL_0008: ldloc.0
   IL_0009: call void [mscorlib]System.Array::Reverse(class [mscorlib]System.Array)
   IL_000e: nop
   IL_000f: ldloc.0
   IL_0010: newobj instance void [mscorlib]System.String::.ctor(char[])
   IL_0015: stloc.1
   IL_0016: br.s IL_0018
   IL_0018: ldloc.1
   IL_0019: ret
} // end of method Helpers::Reverse

In case the library with extension methods is compiled into another namespace  (maybe in another .dll file), you need to bring these into scope using the using statement. An example will make this clear:

using System;
using System.Collections.Generic;

namespace Bar
{
   using Foo;

   class Ext
   {
      public static void Main()
      {
         string s = "Hello world!";
         string r = s.Reverse();
         Console.WriteLine(r);

         List<string> lst = new List<string> { "Bart", "Steve", "Rob", "John", "Bill" };
         foreach (string t in lst.Where(i => i.Length == 4))
            Console.WriteLine(t);
      }
   }
}

namespace Foo
{
   delegate R Func<T,R>(T t);

   static class Extensions
   {
      public static string Reverse(this string s)
      {
         char[] c = s.ToCharArray();
         Array.Reverse(c);
         return new string(c);
      }

      public static IEnumerable<T> Where<T>(this IEnumerable<T> sequence, Func<T, bool> predicate)
      {
         foreach (T item in sequence)
            if (predicate(item))
               yield return item;
      }
   }
}

In the code fragment above you can see the use of using Foo to bring extensions defined in the Foo namespace in scope. This makes the Reverse and Where methods available on System.String and System.Collections.Generic.IEnumerable<T> respectively. The latter method is a sample of an extension method applied to an interface, which is as easy as an extension method on a regular type. To understand the Where method sample, you should know about iterators (a C# 2.0 feature). You can read more on iterators on my C# 2.0 Iterators blog post. The Where method sample is equivalent to the Where operator in LINQ and can be found in my LINQ-SQO CodePlex project as well. Essentially, it takes a predicate (which is a method that maps an "item" on a boolean value) to filter items from the source "sequence" (a synonym for an IEnumerable<T> collection type). In the samp,e all strings with a length of 4 characters are retained.

Tke key takeaway from this post is that extension methods are a convenient way to extend types with instance methods without requiring access to the orginal source code. As usual, use it with care. LINQ uses extension methods extensively, as we'll explain later on. Have fun!

kick it on DotNetKicks.com

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# re: C# 3.0 Feature Focus - Part 4 - Extension Methods

Wednesday, December 06, 2006 8:41 PM by John Rusk

Bart, What do you think about the possible versioing problems with extension methods? I blogged about it here, http://dotnet.agilekiwi.com/blog/2006/04/extension-methods-problem.html . Everyone is is talking about segregating extension methods into their own namespaces, but I'm not sure that completely solves the problem.

# C# 3.0 Extension Method Versioning Troubles - Some thoughts and random ideas

Sunday, December 10, 2006 1:21 PM by B# .NET Blog

A few days ago I blogged about extension methods in C# 3.0 as a piece of glue to support LINQ ( although

# The IQueryable tales - LINQ to LDAP - Part 0

Thursday, April 05, 2007 7:04 PM by B# .NET Blog

Here we are again for some cool LINQ stuff. In the past I've been blogging on C# 3.0 language innovation

# The IQueryable tales - LINQ to LDAP - Part 1: Key concepts

Friday, April 06, 2007 8:14 AM by B# .NET Blog

Introduction Welcome to the first real part of our LINQ-to-LDAP series. So far, we've been discussing:

# Extension Methods in Windows PowerShell

Thursday, October 11, 2007 3:31 PM by Elan Hasson's Favorite Blogs

You&#39;ve probably already heard about this new feature in .NET 3.5: extension methods. If not, check

# C# 3.0 Feature Focus – Link Collection

Saturday, August 09, 2008 7:13 AM by B# .NET Blog

Collecting a few of my posts for easy quick reference: C# 3.0 Feature Focus - Part 1 - Local Type Inference

# Are Extension Methods Dangerous? : Acceptably Unique

Saturday, June 20, 2009 4:35 AM by Are Extension Methods Dangerous? : Acceptably Unique

Pingback from  Are Extension Methods Dangerous? : Acceptably Unique