When calling an extension method on an expression containing an elvis operator (i.e. the nullsafe dereferencing operator; ?.) the resulting null is not passed to the extension method as expected. In essence, it might cause unexpected null reference exceptions. Here's a program demonstrating this:
class Program
{
static void Main(string[] args)
{
string nil = null;
foreach (var c in ((nil?.ToCharArray()).EmptyIfDefault())) { }; // works
foreach (var c in (nil?.ToCharArray().EmptyIfDefault())) { }; // nullref
}
}
public static class Utility
{
public static char[] EmptyIfDefault(this char[] target)
{
return target ?? new char[0];
}
}
Does anybody if this behavior is by design? Notice there's no ? between the ToCharArray() and the EmptyIfDefault. If there was, I'd understand the current behavior. Right now, it seems like a bug. (What's the correct way to report this to Microsoft?)
For others who see the same behavior: the extra braces seem to prevent it.
(By the way: here's the actual EmptyIfNull I'm using:)
public static IEnumerable<TTarget> EmptyIfNull<TTarget>(this IEnumerable<TTarget> target)
{
return target ?? Enumerable.Empty<TTarget>();
}
edit I'll just include the answer given below into my question:
It is related to a common pitfall:
var txt = "I am " +
age>=18 ? "mature" : "not old" +
" enough.";
This is also interpreted as
var txt = "I am " +
age >= 18
? "mature"
: ("not old" + " enough.");
When rewritten, The behavior without braces makes sense:
foreach(var c in
nil == null
? null
: nil.ToCharArray().EmptyIfDefault()) { }; // nullref
While it's unintuitive at first, it definitely is not a bug. You're getting a NullReferenceException
because you're trying to iterate over null
(it's not throwing the exception while evaluating the expressions)
Let's look at this example:
var t = nil?.ToCharArray().EmptyIfNull();
The above will not call EmptyIfNull
, because nil
will be null
and the method chain will short-circuit to return null
.
That is, we can write the above as:
IEnumerable<char> t;
if (nil != null)
t = nil.ToCharArray().EmptyIfNull();
else
t = null;
Note that the EmptyIfNull
only executes if the initial condition passes (that is, nil
is not null).
Now, why do brackets fix it?
var t = (nil?.ToCharArray()).EmptyIfNull();
This can be re-written as:
IEnumerable<char> t;
IEnumerable<char> temp;
if (nil != null)
temp = nil.ToCharArray();
else
temp = null;
t = temp.EmptyIfNull();
See that the short-circuiting behavior only applies to the inner expression - and then we always call EmptyIfNull
on the result.