In my opinion, one of the most marginalized features of C# 3.0 are extension
methods. The idea behind them is to provide a way to extend a class with new methods
by allowing static methods to be called using instance method syntax. Here's a simple
example of how they can be used:
using System;
namespace ExtensionTest
{
static class Program
{
static int ToInt32(this
string s)
{
return
Int32.Parse(s);
}
static void Main(string[]
args)
{
string text =
"1000";
int textValue = text.ToInt32() + 1;
Console.WriteLine(textValue);
}
}
}
The above code declares a ToInt32() extension method for the System.String class.
In the Main() method, ToInt32() is called using instance method syntax on a local
string variable. As expected, this program prints 1001 to the console when run.
One interesting side effect of extension methods is that they can be called on
a null reference without raising a NullReferenceException. Let's expand the sample
code a bit to show what I mean:
using System;
namespace ExtensionTest
{
static class Program
{
static int ToInt32(this
string s)
{
if (String.IsNullOrEmpty(s))
return 0;
return
Int32.Parse(s);
}
static void Main(string[]
args)
{
string text =
null;
int textValue = text.ToInt32() + 1;
Console.WriteLine(textValue);
}
}
}
The updated sample doesn't raise a NullReferenceException when text.ToInt32()
is called—even though text is a null reference. Instead, the method returns 0 and
1 is printed to the console.
Obviously, this side effect can be a bit of a land mine and it is good practice
to allow NullReferenceExceptions to be thrown from extension methods so that everything
works as expected. But, there are a couple of cases where this side effect can be
used to great effect.
Over at Phil Haack's blog, there is a bit
of
discussion about the String.IsNullOrEmpty static method that was added in .NET
Framework 2.0. In the comments section,
Jon Galloway wrote the following:
""
The next comment states that this doesn't make sense because you can't call an instance
method on a null reference. However, you can call an extension method on
a null reference. Here is how you could achieve a String.IsNullOrEmpty() instance
method that really works:
using System;
namespace StringExtensionTest
{
static class Program
{
static bool IsNullOrEmpty(this
string s)
{
return (s ==
null
|| s.Length == 0);
}
static void Main(string[]
args)
{
string nullText =
null;
string emptyText =
String.Empty;
string realText =
"Dustin has a trophy wife";
Console.WriteLine("nullText.IsNullOrEmpty: {0}", nullText.IsNullOrEmpty());
Console.WriteLine("emptyText.IsNullOrEmpty: {0}", emptyText.IsNullOrEmpty());
Console.WriteLine("realText.IsNullOrEmpty: {0}", realText.IsNullOrEmpty());
}
}
}
Thanks to the null-reference side effect of extension methods, the above program
writes this to the console:
nullText.IsNullOrEmpty: True
emptyText.IsNullOrEmpty: True
realText.IsNullOrEmpty: False
In my opinion, taking advantage of the side effect in this case is OK because
the name of the method is descriptive. In addition, this allows us to add another
method to the .NET framework that is sorely missing: Array.IsNullOrEmpty():
using System;
namespace ArrayExtensionTest
{
static class Program
{
static bool IsNullOrEmpty(this
Array array)
{
return (array ==
null || array.Length == 0);
}
static void Main(string[]
args)
{
string[] nullArray =
null;
string[] emptyArray = { };
string[] realArray = {
"Dustin", "Dustin's Trophy
Wife" };
Console.WriteLine("nullArray.IsNullOrEmpty: {0}", nullArray.IsNullOrEmpty());
Console.WriteLine("emptyArray.IsNullOrEmpty: {0}", emptyArray.IsNullOrEmpty());
Console.WriteLine("realArray.IsNullOrEmpty: {0}", realArray.IsNullOrEmpty());
}
}
}
For my own forays into C# 3.0, I've placed both of these extension methods inside
the System namespace. That way, all I have to do is reference the assembly containing
my extensions and they will always be available:
namespace System
{
public static class
SystemExtensions
{
public static bool IsNullOrEmpty(this
Array array)
{
return (array ==
null || array.Length == 0);
}
public static bool IsNullOrEmpty(this
string s)
{
return (s ==
null
|| s.Length == 0);
}
}
}
