共用方式為


Extension methods in C# 3.0

You can think of extension methods as the ability to add instance methods to already existing types. Having said that, I would immediately also tell you that it is just an illusion. You are not actually adding a method to an existing type. It is just a little trick that the compiler plays on you!

Let’s pretend that we want to write a simple static method which takes a collection of numbers and returns the average value of those numbers (for my functional programming fans, it should probably be implemented using a foldl).

This aggregate method would look something like this:

namespace CoolExtensions

{

   public static class Extensions

   {

      public static double Average(IEnumerable<double> doubles)

      {

         double result = 0;

         int counter = 0;

         foreach (int d in doubles)

         {

            result += d;

            counter++;

         }

         return result / counter;

      }

   }

}

If I want to make this into an extension method – a method that extends IEnumerable<double> - I can just use the this modifier on the first argument of the method.

public static double Average(this IEnumerable<double> doubles) { ...

Using the this modifier turns this static method into an extension method on any type which implements the IEnumerable<double> interface. If the namespace in which this extension method is defined is in scope (using CoolExtensions;), this method can be called as an instance method on any type that implements that interface. For example:

double[] doubles = new double[] { 1, 2, 3 };

// both statements below are semantically equivalent

double avg = Extensions.Average(doubles);

double avg = doubles.Average(); // extension method as an instance method

The above extension method works because doubles is of type double[] which implements the IEnumerable<double>. You also notice that when the method is used as an extension method, it loses the first argument. Visual Studio codename Orcas has IntelliSense enabled for extension methods with a different icon to regular instance methods:

Here are some more examples of extension methods:

public static class Extensions

{

 static TResult M1(this TSource source) ...

 static TResult M2(this TSource source, TArg0 arg0) ...

 static TResult M2(this TSource source, TArg0 arg0, TArg1 arg1) ..

 static TResult M4<T>(this TSource source, TArg0 arg0) ...

And here is how they can be called:

Extensions.M1(source);

source.M1(); // extension method as an instance method

Extensions.M2(source, arg0);

source.M2(arg0); // extension method as an instance method

Extensions.M3(source, arg0, arg1);

source.M3(arg0, arg1); // extension method as an instance method

Extensions.M4<T>(source, arg0);

source.M2<T>(arg0); // extension method as an instance method

 

As you can see, an extension method can still be called like an ordinary static method.

Some random but important points regarding extension methods from the C# 3.0 Specification:

- Extension methods can only be declared in static, non-generic and non-nested classes.

- The first parameter of an extension method can only have the this modifier (no other modifiers are allowed) and the parameter type cannot be a pointer type.

- An extension method becomes available in a namespace as long as it is declared in a static class within the namespace or is imported through the using namespace directive.

- Extension methods have lower precedence than regular instance methods on a type.

The way that the lookup of an instance method call is handled at compile time is similar to how it worked in C# 2.0 but with an additional lookup phase for extension methods. In order to illustrate this additional lookup phase, assume the following extension method which calculates the sum of a sequence of integers:

public static class Extensions

{

    public static int Sum(this IEnumerable<int> ints)

    {

        int sum = 0;

        foreach (int i in ints) sum += i;

        return sum;

    }

}

This method can now simply be used as an instance method on instances of types that implement the IEnumerable<int> interface:

int[] ints = new int[] { 1, 2, 3 };

ints.Sum();

In this example, the compiler will look at the type of int[] and attempts to find an instance method called Sum with no parameters. If such a method is found then the lookup is complete. However, if this method is not found, an extra lookup phase is performed. This time, the compiler will transform the signature of the method in the following manner:

IEnumerable<int>.Sum() è  XXX.Sum(IEnumerable<int> ints)

The new signature is then used to find an extension method in static classes that are in scope.

Extension methods are less discoverable and more limited in functionality. In some scenarios they may also decrease the readability of your code. For these reasons, it is recommended to use extension methods sparingly and only in cases where instance methods are not feasible. To illustrate this point, assume the following extension method on all types (an extension method on the object type):

public static void Print(this object obj)

{

    Console.WriteLine(obj.ToString());

}

With this method, you can now use the Print instance method on any type including value types:

123.Print();

12.8976.Print();

"string".Print();

Although this can be fun, it is not hard to see why it might not be the best solution.

Extension methods are often used in the context of query expressions which is another new feature in C# 3.0:

double[] doubles = new double[] { 1, 2, 3 };
var q =
from d in doubles
where d > 2
select d;

The above query expression is semantically identical to the following method calls on the source (doubles):

var q =

    doubles

     .Where(d => d > 2)

     .Select(d => d);

Both Where and Select could be instance or extension methods on the type of the source.

It is needless to say that you cannot use reflection to discover an extension method as an instance method. The listing below returns null for mi:

IEnumerable<int> ints = new int[] { 1, 2, 3 };

int p = ints.Sum();

MethodInfo mi = ints.GetType().GetMethod("Sum");

Comments

  • Anonymous
    January 09, 2008
    Pedram,     I am experiencing the same issue (The listing returns null for mi). Have you been able to determine how intellisense is able to discover the extension method?

  • Anonymous
    January 09, 2008
    JH, IntelliSense certainly does not use reflection in here. Let’s remember, Extension methods are a compile-time trick played by the compiler. They are effectively nothing more than a static method. Reflection can only expose methods which are real methods exposed by your type.

  • Anonymous
    January 09, 2008
    Yeahp... I knew it couldn't be using reflection... I'm wondering what mechanism it's using to discover the extension methods.

  • Anonymous
    January 09, 2008
    JH, the IntelliSense cannot rely only on the metadata included in a compiled assembly because in many cases you need IntelliSense on the code which does not even compile. I don’t know the mechanisms used by the C# IntelliSense to achieve this but I would suspect that it uses the parsing capabilities of the C# parser to build a code tree.