C# – Extension Methods

Extension Methods

Allow existing type to be extended with new methods without altering the definition of the original type.

An extension method is a static method of a static class, where modifier is applied to the first parameter.

// Extension methods allow an existing type to be extended with new methods without altering
// the definition of the original type:

// (Note that these examples will not work in older versions of LINQPad)

static void Main()
{
	Console.WriteLine ("Perth".IsCapitalized());	
	// Equivalent to:
	Console.WriteLine (StringHelper.IsCapitalized ("Perth"));	
	
	// Interfaces can be extended, too:
	Console.WriteLine ("Seattle".First());   // S
}

public static class StringHelper
{
	public static bool IsCapitalized (this string s)
	{
		if (string.IsNullOrEmpty(s)) return false;
		return char.IsUpper (s[0]);
	}
	
	public static T First<T> (this IEnumerable sequence)
	{
		foreach (T element in sequence)
			return element;
		
		throw new InvalidOperationException ("No elements!");
	}
}

Extension Method Chaining

// Extension methods, like instance methods, provide a tidy way to chain functions:

static void Main()
{
	string x = "sausage".Pluralize().Capitalize();
	x.Dump();
	
	// Equivalent to:
	string y = StringHelper.Capitalize (StringHelper.Pluralize ("sausage"));
	y.Dump();
	
	// LINQPad's Dump method is an extension method:
	"sausage".Pluralize().Capitalize().Dump();
}

public static class StringHelper
{
	public static string Pluralize (this string s) => s + "s";   // Very naiive implementation!
	
	public static string Capitalize (this string s) => s.ToUpper();
}

Ambiguity and Resolution

Namespaces

An extension method cannot be accessed unless its class is in scope, by importing its namespace.

using System;

namespace Utils
{
   public static class StringHelper
   {
      public static bool IsCapitalized (this string s)
      {
         if(string.IsNullOrEmpty(s)) return false;
         return char.IsUpper(s[0]);
      }
   }
}

To use IsCapitalized, need to import Utils namespace

namespace MyApp
{
   using Utils;
   class Test
   {
      static void Main() => Console.WriteLine ("Perth".IsCapitalized());
   }
}

Extension methods versus instance methods

// Any compatible instance method will always take precedence over an extension method:

static void Main()
{
	new Test().Foo ("string");	// Instance method wins, as you'd expect
	new Test().Foo (123);		// Instance method still wins
}

public class Test
{
	public void Foo (object x) { "Instance".Dump(); }    // This method always wins
}

public static class StringHelper
{
	public static void Foo (this UserQuery.Test t, int x) { "Extension".Dump(); }
}

Extension methods versus extension methods

// The extension method with more specific arguments wins. Classes & structs are
// considered more specific than interfaces:

static void Main()
{
	"Perth".IsCapitalized().Dump();
}

static class StringHelper
{
	public static bool IsCapitalized (this string s)
	{
		"StringHelper.IsCapitalized".Dump();
		return char.IsUpper (s[0]);
	}
}

static class EnumerableHelper
{
	public static bool IsCapitalized (this IEnumerable s)
	{
		"Enumerable.IsCapitalized".Dump();
		return char.IsUpper (s.First());
	}
}
// The extension method with more specific arguments wins. Classes & structs are
// considered more specific than interfaces:

static void Main()
{
	string[] strings = { "a", "b", null, "c"};
	foreach (string s in strings.StripNulls())
		Console.WriteLine (s);
}

static class Test
{
	public static IEnumerable StripNulls (this IEnumerable seq)
	{
		foreach (T t in seq)
			if (t != null)
				yield return t;
	}
}