Understanding C# 3.0 Features (5) Extension Method

[LINQ via C# series]

Extension method is a fancy and powerful syntactic sugar in C# 3.0. Extension methods are very important when writing functional style C# code.

Define an extension method for a class

When we define an extension method for a type, this extension method must:

  • be a static method
  • be defined in a static class
  • have the first parameter to be that type, and add a “this” keyword preceded

For example, an extension method can be defined for the Person class in part 1:

public class Person
{
    public Person(string name)
    {
        this.Name = name;
    }

    public string Name
    {
        get;
        private set;
    }
}

public static class PersonExtensions
{
    public static void Speak(this Person person)
    {
        Console.WriteLine(
            "[{0}] {1} is speaking.",
            DateTime.Now.ToString(CultureInfo.InvariantCulture),
            person.Name);
    }

    public static void Speak(this Person person, string content)
    {
        Console.WriteLine(
            "[{0}] {1} is speaking: {2}.",
            DateTime.Now.ToString(CultureInfo.InvariantCulture),
            person.Name,
            content);
    }
}

Invocation

With the C# 3.0 syntactic sugar, the extension method can be invoked as if we “added” an instance method for the Person object:

Person person = new Person("Mark");
person.Speak();
person.Speak("I like Alienware M17x.");

Compilation

Obviously, PersonExtension’s static method Speak() can be regarded as Person’s instance extension method because of the help of “this” keyword before its first parameter.

“this” will cause the definition code compile into:

public static class PersonExtensions
{
    [Extension]
    public static void Speak(Person person)
    {
        Console.WriteLine(
            "[{0}] {1} is speaking.",
            DateTime.Now.ToString(CultureInfo.InvariantCulture),
            person.Name);
    }
}

So an extension method is nothing but a normal static method.

Then when the compiler sees the Speak() method invocation on the Person instance, it searches for an available Speak() in the context. According to C# Version 3.0 Specification, the search order is:

  • instance method in the type definition
  • extension method in the current namespace
  • extension method in the current namespace’s parents namespaces
  • extension method in the other namespaces imported by “using”

Once compiler finds a first match (in this case it is the method in PersonExtensions), it compiles extension method invocations into normal static method invocations:

Person person = new Person("Mark");
PersonExtensions.Speak(person);
PersonExtensions.Speak(person, "I like Alienware M17x.");

One more example, if an instance method is defined on the Person type:

public class Person
{
    public void Speak()
    {
    }
}

Then the following invocation:

Person person = new Person("Mark");
person.Speak();

will be compiled to Person.Speak(), not PersonExtensions.Speak().

Static method vs. instance method

After defining a static method, it is invoked as an instance method. This looks more or less confusing. For better understanding the fact, consider the difference between static method and instance method:

public class Person
{
    public Person(string name)
    {
        this.Name = name;
    }

    public string Name
    {
        get;
        private set;
    }

    public void Speak(string content) // Instance method.
    {
        Console.WriteLine("{0} is speaking: {1}.", this.Name, content);
    }

    public static int CompareNames(Person personA, Person personB) // Static method.
    {
        return personA.Name.CompareTo(personB.Name);
    }
}

Take a look at the compile results:

.method public hidebysig instance void Speak(string content) cil managed
{
    .maxstack 8
    L_0000: nop 
    L_0001: ldstr "{0} is speaking: {1}."
    L_0006: ldarg.0 // Loads the first argument "this".
    L_0007: call instance string LinqViaCSharpSamples.Person::get_Name()
    L_000c: ldarg.1 // Loads the second argument "content".
    L_000d: call void [mscorlib]System.Console::WriteLine(string, object, object)
    L_0012: nop 
    L_0013: ret 
}

.method public hidebysig static int32 CompareNames(class LinqViaCSharpSamples.Person personA, class LinqViaCSharpSamples.Person personB) cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 CS$1$0000)
    L_0000: nop 
    L_0001: ldarg.0 // Loads the first argument "personA".
    L_0002: callvirt instance string LinqViaCSharpSamples.Person::get_Name()
    L_0007: ldarg.1 // Loads the second argument "personB".
    L_0008: callvirt instance string LinqViaCSharpSamples.Person::get_Name()
    L_000d: callvirt instance int32 [mscorlib]System.String::CompareTo(string)
    L_0012: stloc.0 
    L_0013: br.s L_0015
    L_0015: ldloc.0 
    L_0016: ret 
}

The difference is:

  • for a static method, the arguments are exactly the parameters declared;
  • for a instance method, the actual first argument is the “this” reference, and the first parameter your declared becomes the second argument.

So there is no problem if regarding this extension method:

public static void Speak(this Person person, string content)
{
    Person arg0 = person;
    string arg1 = content;
}

as an instance method of a Person object:

public void Speak(string content)
{
    Person arg0 = this;
    string arg1 = content;
}

Extension methods for other types

Besides classes, we can also define extension methods for structs, interfaces, delegates, etc.

Scenarios for structs are infrequent. Here is a useful sample extension method for the IEnumerbale<T> interface:

public static void ForEach<TSource>(
    this IEnumerable<TSource> source, 
    Action<TSource> action)
{
    if (source == null)
    {
        throw new ArgumentNullException("source");
    }

    if (action == null)
    {
        throw new ArgumentNullException("action");
    }

    foreach (TSource item in source)
    {
        action(item);
    }
}

With this extension method, we can write code like:

int[] numbers = new int[] { 0, 1, 2, 3, 4 };
numbers.ForEach(number => Console.WriteLine(number));

or

int[] numbers = new int[] { 0, 1, 2, 3, 4 };
numbers.ForEach(Console.WriteLine);

The above ForEach() method provides programmatic convenience as well as side effects, which will be mentioned later. My friend Mark also has an excellent post about this: Why no ForEach method on IEnumerable interfaces.

Here is another sample extension method for IQueryable<T>.Page() from my post C# Coding Guidelines (6) Documentation:

public static IQueryable<TSource> Page<TSource>(this IQueryable<TSource> source, int pageIndex, int pageSize)
{
    if (source == null)
    {
        throw new ArgumentNullException("source");
    }

    if (pageIndex < 0)
    {
        throw new ArgumentOutOfRangeException("pageIndex", Resource.PageIndexShouldNotBeNegative);
    }

    if (pageSize < 0)
    {
        throw new ArgumentOutOfRangeException("pageSize", Resource.PageSizeShouldNotBeNegative);
    }

    return source.Skip(pageIndex * pageSize).Take(pageSize);
}

Other samples, like extension method for delegates, will also be introduced in later posts.

Published Saturday, November 28, 2009 1:00 PM by Dixin

Comments

# re: Understanding C# 3.0 Features (5) Extension Method

Tuesday, March 27, 2012 7:40 AM by Cody

Wow! I could not even guess about it)) Not bad.

Leave a Comment

(required) 
(required) 
(optional)
(required)