(You are currently looking at the first edition version of this page. This page is also available for the second and third editions.)

Notes for Chapter 10: Extension methods

10.2: Extension delegates

There's one feature I wasn't even aware of when writing the book. I only found out about it when reading the preview of another C# 3 book by Bruce Eckel and Jamie King.

Basically, the feature allows you to specify an extension method as a method group using extension syntax. It can then be converted to a delegate of an appropriate type as if it were an instance method. As an example, take the Count() extension method on IEnumerable<T>. The actual declared method is static and takes a single parameter, so in some ways we shouldn't logically be able to use it as a the target of an Func<int> delegate which takes no parameters and returns an int, right? Nope...

using System;
using System.Linq;

public class Test
    static void Main()
        string[] x = {"a""b""c"};
        // Call extension method directly
        // Convert extension method group to delegate
        Func<int> func = x.Count;
        // Print out the target of the delegate
        // Print out the value returned by calling the delegate

This compiles and executes, with this output:


Note how the target of the delegate is the array, even though delegates which use static methods normally have a null target.

Basically, the long and the short of it is that extension methods can be used as if they were instance methods, not just for calling, but also for conversion into delegates. That's really neat.

10.2.4: Null references vs null values

As I mention in section 10.2.4 when describing extension methods, "You can't call instance methods on null references in C#". This is correct (with the understanding that calling an extension method using a null target isn't the same as calling a true instance method) but life becomes more interesting when you consider null values in general. In particular, Nullable<T> allows various methods to be called on a null value. Consider the following code:

int? foo = null;
int hash = foo.GetHashCode();
string text = foo.ToString();
bool equal = foo.Equals(foo);
Type type = foo.GetType();

Without peeking back at chapter 4, what would you expect the results to be? As it happens, the code above does blow up with an exception - but only on the last line. The first three method calls require no boxing, and Nullable<T> handles them just fine. The call to GetType does end up boxing the value into a null reference, which then causes a NullReferenceException.

Many thanks to Marc Gravell for this interesting example.

10.2.4: Extra issue when reusing existing names for extension methods

In section 10.2.4, I give an example of a potentially useful extension method: effectively making string.IsNullOrEmpty usable as if it were an instance method.

Now, I already give a warning about potentially confusing people reading your code, if they're used to it being a static method - but Intellisense makes this even worse. Here's a screenshot from Visual Studio 2008 SP1. I've declared two extension methods: IsNullOrEmpty and IsNullOrWhitespace. I've then captured the screen after typing "foo." where foo is a variable of type string.

Intellisense bug around extension methods in VS2008

Note how Intellisense is providing different icons for the two extension methods - it's still displaying the normal "static method" icon for IsNullOrEmpty.

It's not hugely serious, but it's just another thing to be aware of. I should note that ReSharper has its own Intellisense display, and that does the right thing.

10.3.1: Features of Enumerable.Range

One feature of Enumerable.Range which isn't supported by the Range class in chapter 6 is the ability to create an empty range. Because both ends of the range are inclusive, you always end up with at least one entry.

This made the code in chapter 6 simpler, but it's nice to be able to specify an empty range. The "real" range class in MiscUtil includes this feature, of course.

10.3.1: Detecting performance problems

Deferred execution has a downside: if you write a query which will take a long time to execute, your profiler is unlikely to point you at the query creation point, which is where you're likely to be able to fix it. If you try to create queries close to the code which iterates over the results, you may find it easier to understand the performance characteristics.

10.3.2: Efficiency of examples

Before technical review, the query in listing 10.8 called Where after Reverse - in other words, it was inefficient. I knew about this, and already had the callout to explain how the efficiency could be improved, but Eric suggested that the code in the listing should be the more efficient code to start with.

His reasoning (which I totally agree with) is that sometimes developers take code directly from books, and then fiddle with it until it works for their particular situation - sometimes without reading the surrounding text. Therefore the examples should avoid errors which are then pointed out in the text.

The moral of the story is two-fold:

10.4.1: Extending the world... carefully

Eric has a bit of guidance about the use of extension methods:

Future versions of the frameworks design guidelines will probably say "please don't put extension methods on object, System.ValueType, System.Enum, unconstrained type parameters, etc."

We will violate this guideline ourselves in a few key places—a lot of people are asking for an In operator which is the inverse of Contains. static bool In<T>(this T, IEnumerable<T> ts) { ... } so you can say if 12.In(myints). A bit bogus if you ask me, but people like it. Clearly this would then be an extension method which matches every type.

There's one use of extension methods like this that has proved useful to me - acting on anonymous types, just by reflecting over their properties. I do this in chapter 12 with AsXAttributes and I've also got a version in MiscUtil for AsXElements.

I also have an extension method for any reference type, which throws an ArgumentNullException (with optional parameter name) is you call it on a null reference. This make argument checking easy - I just type foo.ThrowIfNull("foo");.