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;
}
}