Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I have to place () around null-conditional expression to use the correct method overload?

Tags:

I have these extension methods and enum type:

public static bool IsOneOf<T>(this T thing, params T[] things)
{
    return things.Contains(thing);
}

public static bool IsOneOf<T>(this T? thing, params T[] things) where T : struct
{
    return thing.HasValue && things.Contains(thing.Value);
}

public enum Color { Red, Green, Blue }

The first if below compiles; the second does not:

 if ((x.Y?.Color).IsOneOf(Color.Red, Color.Green))
 ;
 if (x.Y?.Color.IsOneOf(Color.Red, Color.Green))
 ;

They only vary by the extra set of parentheses. Why do I have to do this?

At first I suspected it was doing a double implicit cast from bool? to bool and then back to bool?, but when I remove the first extension method, it complains there is no implicit cast from bool to bool?. I then checked the IL and there were no casts. Decompiling back to C# yields something that looks like:

if (!(y != null ? new Color?(y.Color) : new Color?()).IsOneOf<Color>(new Color[2]
{
    Color.Red,
    Color.Green
}));

which is fine for the version of the CLR I'm running, and what I expect. What I didn't expect is that x.Y?.Color.IsOneOf(Color.Red, Color.Green) doesn't compile.

What is going on? Is it simply the way the language was implemented that it requires the ()?

Update

Here's a screen cap showing the error in context. This is getting even more confusing to me. The error actually makes sense; but what doesn't (in my mind now) is why line 18 wouldn't have the same problem.

enter image description here

like image 439
Kit Avatar asked May 24 '16 19:05

Kit


People also ask

How to use null-conditional operator in c#?

Available in C# 6 and later, a null-conditional operator applies a member access, ?. , or element access, ?[] , operation to its operand only if that operand evaluates to non-null; otherwise, it returns null . That is, If a evaluates to null , the result of a?. x or a?[x] is null .

What is a conditional null?

The null-conditional operators are short-circuiting. That is, if one operation in a chain of conditional member or element access operations returns null , the rest of the chain doesn't execute.

Is null operator c#?

Null-coalescing Operator(??) Null-coalescing Operator is a binary operator that simplifies checking for null values. it is used to define a default value for nullable value types or reference types. It returns the left-hand operand if the operand is not null; otherwise, it returns the right operand.


1 Answers

First of all, this behaviour looks intentional to me. It's rare that somebody adds extension methods to nullable types, and it's much common that people mix null-conditional and regular member accesses in one expression, so the language is favoring the latter.

Consider the following examples:

class B { bool c; }
class A { B b; }
...

A a;
var output = a?.b.c; // infers bool?, throws NPE if (a != null && a.b == null)
// roughly translates to
// var output = (a == null) ? null : a.b.c;

while

A a;
var output = (a?.b).c; // infers bool, throws NPE if (a == null || a.b == null)
// roughly translates to
// var output = ((a == null) ? null : a.b).c;

and then there is

A a;
var output = a?.b?.c; // infers bool?, *cannot* throw NPE
// roughly translates to
// var output = (a == null) ? null : (a.b == null) ? null : a.b.c;

// and this is almost the same as
// var output = (a?.b)?.c; // infers bool?, cannot throw NPE
// Only that the second `?.` is forced to evaluate every time.

The design goal here seems to be aiding the distinction between a?.b.c and a?.b?.c. If a is null, we expect getting an NPE in none of the cases. Why? Because there is a null-conditional directly after a. So the .c part must be only evaluated if a was not null, making the member access dependent of the previous null-conditionals outcome. By adding explicit brackets, (a?.b).c we enforce the compiler to try to access .c of (a?.b) regardless of a being null, preventing it to "short circuit" the whole expression to null. (using @JamesBuck -s words)

In your case, x.Y?.Color.IsOneOf(Color.Red, Color.Green) is like a?.b.c. It will call the function with the signature bool IsOneOf(Color red) (so the overload where the param is not nullable, and I stripped the generic part) only when x.Y was not null, thus wrapping the expression's type in Nullable to handle the case when x.Y is null. And because the while evaluates to bool? instead of bool, it cannot be used as test in an if statement.

like image 159
Tamas Hegedus Avatar answered Sep 17 '22 15:09

Tamas Hegedus