I have a method like this, which is a simplified version of some logic in the production code:
static void Foo(int a, int b, string x, string y)
{
if (a > 100 && b < 50 && (x != null) & (y != null))
{
Console.WriteLine(y.Length);
}
}
I get a "Dereference of a possibly null reference" error when accessing the y.Length
.
If I change &
to &&
, the warning goes away. I couldn't figured it out why at first glance, then I thought it may be because the &
operator is overloadable, so the behaviour might change and possibly it could evaluate to true
even when the y
is null. Is my assumption correct or am I missing something else?
Note: I can reproduce this in an app that targets .NET Core 3.1 or .NET Standard 2.0 using Visual Studio 2019.
There are a few reasons the compiler doesn't recognize this pattern. The most obvious is just engineering effort. Using &
/|
for boolean logic is just seen as less common than &&
/||
and therefore other things were prioritized over full support for &
/|
in flow analysis.
The other reason is that there would actually be a complexity cost to supporting this in nullable analysis. Consider the following admittedly contrived example: SharpLab
func(null, null, out _);
void func(C? x, C? y, out C z)
{
if (x != null & func1(z = y = x)) // should warn on 'y = x'
{
x.ToString(); // warns, but perhaps shouldn't
y.ToString(); // warns, but perhaps shouldn't
}
}
bool func1(object? obj) => true;
class C
{
public C Inner { get; set; }
public C()
{
Inner = this;
}
}
x != null
, the state of x
is "not null when true", and "maybe null when false".func1(z = y = x)
, we have to assume the worst case--x
may be null, since we get there regardless of whether x != null
was true or false. Because of this we have to give a warning on assignment to non-nullable out parameter z
.&
operator, we have to assume that if the result was true, then all the operands returned true, and otherwise any of the operands could have returned false.x
was really not-null all along, and because x
was assigned to y
, that y
was really not-null all along. The only practical way to account for this in this particular kind of analysis is to visit func1(z = y = x)
a second time, but with an initial state where x != null
was true.In other words, visiting a &
operator with full handling of nullable conditional states requires visiting the right-hand side twice--once assuming the worst-case result from the left side to produce diagnostics for it, and again assuming the left side was true, so that we can produce a final state for the operator. In contrived examples this effect can be compounding, such as x != null & (y != null & z != null)
, where we end up having to visit z != null
4 times.
In order to sidestep this complexity, we've opted to not make nullable analysis work with &
/|
at this time. The short-circuiting behavior of &&
/||
means they don't suffer from the problems described above, so it's actually simpler to make them work correctly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With