There is the question of how to handle null
parameters in extension methods. Consider this extension method that returns the number of digits in a string.
public static class MyExtensions
{
public static int CountDigits(this string input)
{
var count = 0;
foreach (var c in input)
{
if (char.IsDigit(c))
{
++count;
}
}
return count;
}
}
Understand, this is just an example. I realize that I can replace all that code with a single function call, and in fact I wouldn’t actually write such a simple extension method to replace that one line of code in my program. Bear with me.
After running the code below, result
will be equal to 10.
string foo = "(555)123-4567";
var result = foo.CountDigits(); // returns 10
So what should happen if I call that method when foo == null
? Let’s see what the Framework does when I call a string
method on a null
reference.
string foo = null;
string lowerFoo = foo.ToLower();
Not surprisingly, that throws NullReferenceException
. And, not surprisingly, the CountDigits
method also throws NullReferenceException
when foo == null
. The difference, though, is important. If you look at the exception detail you’ll find that the call to foo.ToLower
reports the exception as being thrown on that line. The stack trace for the call to CountDigits
looks like this.
System.NullReferenceException was unhandled
HResult=-2147467261
Message=Object reference not set to an instance of an object.
Source=sotesto
StackTrace:
at Test.MyExtensions.CountDigits(String input) in c:\Dev\Jim\Testo2012\sotesto\Program.cs:line 37
at Test.Program.DoStuff() in c:\Dev\Jim\Testo2012\sotesto\Program.cs:line 28
The exception is thrown on the line containing the foreach
statement.
If you understand how extension methods work, this shouldn’t be surprising. After all, the code foo.CountDigits();
is really just shorthand for MyExtensions.CountDigits(foo);
.
The exception is saying that the error exists in the extension method when in reality the error exists at the call site. This is an important distinction. Imagine that you’re calling an extension method that exists in a library for which you have no source. If that extension method throws NullReferenceException
, you will likely assume that the error is in the extension method. And nothing in the stack trace will tell you otherwise. There is nothing to indicate that it threw that exception because you passed a null
parameter.
That is incorrect behavior.
Don’t believe me? Let’s see what happens if I rewrite that extension method.
public static class MyExtensions
{
public static int CountDigits(this string input)
{
return input.Count(char.IsDigit);
}
}
Yes, that duplicates the functionality of the previous version. But if you call it with a null
parameter, the result is quite different.
System.ArgumentNullException was unhandled
HResult=-2147467261
Message=Value cannot be null.
Parameter name: source
Source=System.Core
ParamName=source
StackTrace:
at System.Linq.Enumerable.Count[TSource](IEnumerable`1 source, Func`2 predicate)
at Test.MyExtensions.CountDigits(String input) in c:\Dev\Jim\Testo2012\sotesto\Program.cs:line 36
at Test.Program.DoStuff() in c:\Dev\Jim\Testo2012\sotesto\Program.cs:line 28
Here we see that the Enumerable.Count
extension method threw ArgumentNullException
, and actually told me why: the source
parameter cannot be null
. But, again, it looks like the error is due to the CountDigits
extension method passing a null
argument.
The answer to the question of what to do with null
parameters to extension methods is simple: check them and throw ArgumentNullException
.
public static int CountDigits(this string input)
{
if (input == null)
{
throw new ArgumentNullException("input", "Value cannot be null.");
}
return input.Count(char.IsDigit);
}
Now you know exactly why the error occured: the code that called CountDigits
passed a null
parameter.
The above is how the Framework methods handle null parameters. It’s considered a best practice. More importantly, this is the only way to know for sure where the real error lies.