C# Extension Methods by JavaJeff


C# 3.0's extension methods (static methods that can be invoked using instance method syntax) language feature lets you add methods to existing types without having to derive from those types or recompile source code. Consider the following example:

using System.Linq; 
      // ... 
      int[] grades = { 80, 93, 68, 74, 87, 96 }; 
      Console.WriteLine ("Array sum = "+grades.Sum ()); 
      // Output: Array sum = 498 
      Console.WriteLine ("Array average = "+grades.Average ()); 
      // Output: Array average = 83
 

This example specifies the using System.Linq; directive to introduce Sum(), Average(), and other Language Integrated Query extension methods to an integer array.

If you were to explore the System.Linq namespace, you would discover a public static class named Queryable. Furthermore, you'd discover that this class contains the following declarations for the aforementioned methods:

	  public static double Average(this IQueryable source) 
	  public static int Sum(this IQueryable source)
	  

This collective information tells us what's required to create our own extension methods. (The IQueryable interface is implemented by types that want to serve as query providers, and isn't important to this article.) Specifically, we need to perform three tasks:

  • We must declare our extension methods in a non-generic, non-nested static class. The class must also be public if it's stored in a separate namespace.
  • Each extension method must be declared public and static.
  • Each extension method's first parameter must begin with keyword this, specify no other modifiers, and not be of a pointer type. When invoking the extension method, we won't pass an argument to this parameter.

To clarify these rules, I've created a simple example that adds a Reverse() method to the System.String class for reversing a string — the method creates and returns a new string that contains the reversed content:

	  static class Extension 
	  { 
		public static string Reverse (this string s) 
		{ 
			char[] charArray = s.ToCharArray (); 
			Array.Reverse (charArray); 
			return new string (charArray); 
			} 
		} 
		// ... 
		string s = "abc"; 
		Console.WriteLine (s.Reverse ()); 
		// Output: cba 
		Console.WriteLine ("Hello, C#!".Reverse ()); 
		// Output: !#C ,olleH
		

		string name = "CoderSource.net";
		

Notice that no argument is passed to Reverse() in each of the two method invocations. Also notice that Reverse() is invoked on a string instance (s and "Hello, C#!"): You cannot invoke an extension method on a type (as in String.Reverse()).

I've created another example that demonstrates the usefulness of extension methods. This example introduces an integer-parsing extension method to System.String that never throws an exception, but returns null when a non-integer is detected:

		public static class Extension 
		{ 
			public static int? ParseInt (this String s) 
			{ 
				if (string.IsNullOrEmpty (s)) 
					return null; 
				else 
				{ 
					foreach (char ch in s) 
					if (!Char.IsDigit (ch)) return null; 
					return int.Parse (s); 
				} 
			} 
		} 
		// ... 
		static void Main (string[] args) 
		{ 
			if (args.Length == 1) 
			{ 
				int? value = args [0].ParseInt (); 
				if (value.HasValue) Console.WriteLine (value); 
				else Console.WriteLine ("integer argument expected"); 
			} 
		}
		

The ParseInt() extension method employs the nullable type language feature, which represents the normal range of values for the underlying type (32-bit integer, in this case) plus null, so that it can return null for empty string, null, or string-with-non-digit character arguments.

The examples seem to indicate that extension methods break encapsulation, but this isn't the case. Extension methods cannot access private members because instance method calls (as in s.Reverse ()) are translated into static method calls (as in Extension.Reverse (s)).

Conclusion

Upon encountering a method call, the compiler first looks at the type on which the method is called for a matching instance method. If not found, it looks for a compatible extension method in any static class located in the same namespace. If not found, the compiler reports an error.

This search order implies that extension methods have a lower priority than instance methods. If an instance method has the same signature as an extension method, the compiler will always choose the instance method. As a result, extension methods should be used sparingly.