Wednesday, March 11, 2009

Extension Methods & Null Objects

I ran into an issue that I had learned about a while ago but the code that I was working on predated this. I find it interesting enough to share. I love the various predicate / action methods on List<T> that allows you to interact with a list by passing in a delegate.

I've always been a little dissapointed that these methods aren't part of the IList<T> interface; however, with Extension methods we can rectify this. I defined a new method in a static class as:

        public static T Find<T>(this IList<T> col, Predicate<T> match)

        {

            if (match == null)

            {

                throw new ArgumentNullException("match");

            }

 

            for (int i = 0; i < col.Count; i++)

            {

                if (match(col[i]))

                {

                    return col[i];

                }

            }

            return default(T);

        }



This has been working fine for almost a year now, until today when I got a null Reference exception on the for loop. The interesting thing with Extension methods is they are nothing more then a static method that the compiler does some magic with.

To prove this go ahead and create a simple console application. Add an extension method on your favorite type (I choose a string), and call this from your main method. Here's what I used:

    static class Extensions

    {

        public static void ToMe(this string value)

        {

            if (value == null)

            {

                Console.WriteLine("Null value");

            }

        }

    }



static void Main(string[] args)

        {

            string s = null;

            s.ToMe();

            Console.ReadLine();

        }



After you compile this go ahead and fire up reflector or Ildasm and have a look at the IL. In my case I had the following instruction:
call void ConsoleApplication4.Extensions::ToMe(string)


Since all we are doing is calling a static method and passing in a reference to the variable which the extension method was called off of, it becomes perfectly legal to call extension methods on a null reference.

Now in my opinion this is wrong, and a side effect of the implementation, and we should not rely upon this behavior. The correct code should look like:

       public static T Find<T>(this IList<T> col, Predicate<T> match)

        {

            if (match == null)

            {

                throw new ArgumentNullException("match");

            }

            if (col == null)

            {

                throw new NullReferenceException();

            }

            for (int i = 0; i < col.Count; i++)

            {

                if (match(col[i]))

                {

                    return col[i];

                }

            }

            return default(T);

        }

No comments: