4

var a = b?.c.d;

Shouldn't this expression always give a compile error? If b is null, the null value is propagated through so c will also be null and thus also need this operator. In my understanding usage of this operator in an expression spreads viral.

But neither Visual Studio 2015 nor Resharper says anything to me, am I missing something here?

codymanix
  • 28,510
  • 21
  • 92
  • 151
  • Why would it be a compiler error? If `b` is null then the expression evaluates to null. If it isn't then the expression returns the result of `b.c.d`. – Charles Mager Mar 31 '17 at 14:03
  • 1
    No, if `b` already is null, `c` isn´t evaluated at all, making `a` simply `null`. – MakePeaceGreatAgain Mar 31 '17 at 14:03
  • I don't understand the question; if `b` is `null`, the expression is not evaluated furter, but yields `null`, so there is no chance anything happens to `c`. – Codor Mar 31 '17 at 14:04
  • What compiler error are you expecting to be generated? – Servy Mar 31 '17 at 14:08
  • 1
    Were you maybe thinking of `var a = (b?.c).d;`? – Damien_The_Unbeliever Mar 31 '17 at 14:11
  • @Damien_The_Unbeliever Which *also* compiles – Servy Mar 31 '17 at 14:17
  • @Servy Surely `(b?.c).d` compiles, but it makes little sense to use it (see my answer). Even if something compiles, it can be quite useless. For example `((object)null).GetType()` compiles, I think, but is a silly thing to do. – Jeppe Stig Nielsen Mar 31 '17 at 16:19
  • @JeppeStigNielsen And yet the question asks why it doesn't give a compile error. Apparently the OP doesn't feel that that code should surely compile, despite not explaining why. – Servy Mar 31 '17 at 16:36
  • @Servy, I _think_ the Original Poster thinks `b?.c.d` acts just like `(b?.c).d` because he says: _"If b is null, the null value is propagated through so c will also be null and thus also need this operator."_ I take this to mean that `b?.c.d` would, according to the asker, always be a "typo" for `b?.c?.d`. ___Suppose___ that was how `?.` worked. _Then_ it would be fair to raise a compile-time warning, or error, because, as I say, `(b?.c).d` is never a sensible thing to do. However, as we know, the premise is wrong. The operator `?.` does not work like that. And `b?.c.d` is sensible enough. – Jeppe Stig Nielsen Mar 31 '17 at 17:21
  • @JeppeStigNielsen That you think the code is possibly wrong isn't a reason for the compiler to error. The compiler errors when the code couldn't possibly be compiled. Additionally, there *are* situations where you might actually want the behavior of `(b?.c).d`. They'd be highly unusual, but it can happen. – Servy Mar 31 '17 at 17:26
  • @Servy OK, I think I know what you are saying. I am not agreeing with the Original Poster, I am just trying to guess what he means. Instead of `(b?.c).d` I would certainly write `b.c.d` if `b` was a reference type, or `b.Value.c.d` if `b` was `Nullable<>`. Writing `(b?.c).d` is much too obscure in my opinion. – Jeppe Stig Nielsen Mar 31 '17 at 17:52

4 Answers4

4

The operator is just syntactic sugar for this:

MyType a = b == null ? 
    null: 
    b.c.d;

Why this should throw a compile-error is unclear to me.

If b is null, the null value is propagated through so c will also be null and thus also need this operator

This isn´t true. Actually when b is null c doesn´t even exist as there´s no instance on which that member could exist. So in short the operator just returns null and omits evaluating c or even d any further.

MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
  • That's not a valid conversion. `a` is *always* assigned a new value, although that value might be `null`. – Servy Mar 31 '17 at 14:06
  • `var a = (b == null) ? null : b.c.d;` – Jeroen van Langen Mar 31 '17 at 14:07
  • 2
    Also note that `b` is not actually evaluated multiple times, so your sugar isn't right for that reason either, although it's closer now. – Servy Mar 31 '17 at 14:08
  • An example to illustration the latest comment by @Servy could be if `b` was defined as in: `class CurrentClass { static bool wasCalledBefore; static B { get { if(wasCalledBefore) { return null; } wasCalledBefore = true; return new B { c = new C { d = new D(), }, }; } } }` – Jeppe Stig Nielsen Mar 31 '17 at 17:58
0

This operator does short circuit and returns null in case of b is null.

0
var a = b == null ? null : b.c.d

This is what that statement would look like in the old way, the ?. operator doesn't look deeper when what is before it is null, it just returns null, you WILL however get an error where b is defined but b.c == null since you didn't write as var a = b?.c?.d.

Andrew
  • 1,544
  • 1
  • 18
  • 36
0

Note that:

var a1 = b?.c.d;

is entirely different from:

var a2 = (b?.c).d;

It is not very easy to explain in short how the unary operator ?. works (yes, unary!). But the idea is that the rest of the "chain" of "operations" is skipped if the expression before ?. is null.

So for a1, you get a null of the compile-time type carried by member d (or Nullable<> of that type if necessary) whenever b happens to be null. When b happens to be non-null, you get the same as b.c.d which may fail if b.c is null.

But a2 is quite different. It will always blow up if b is null, because then the parenthesis (b?.c) will be a null reference, and the next . operator will lead to NullReferenceException. (I am assuming here that c has a reference-type at compile-time.)

So do not think there is a kind of "left-associativity" that makes b?.c.d equivalent to (b?.c).d!


You can find a link to the preliminary C# Language Specification in this answer in another thread; they mention Null-conditional operator as an unary operator in § 7.7.1.

Community
  • 1
  • 1
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181