4

In the following code, one of the two variants does not compile:

class C
{
    public decimal DecimalField;
}

static C GetC() { return new C(); } //Can return null in reality.

C c = GetC(); //Get a C value from somewhere, this might be null

string toString1 = c?.DecimalField?.ToString(); //Does not compile.
string toString2 = (c?.DecimalField)?.ToString(); //Compiles.

Error CS0023: Operator '?' cannot be applied to operand of type 'decimal'

Why does the simple form not compile?

Wouldn't the expression c?.DecimalField be of type decimal?? That value can be null so the ?. operator should apply. I'm quite sure this is so because in this code:

var decimalValue = c?.DecimalField;

the var does resolve to decimal? according to the IDE.

5
  • 3
    Because decimal is not nullable Commented Jul 11, 2017 at 12:06
  • 1
    I think the decimal type cannot be null, so the null-coalesce operator makes no sense here. Commented Jul 11, 2017 at 12:06
  • 3
    its not the simple form, adding the parenthesis changes the expression Commented Jul 11, 2017 at 12:07
  • 1
    The obvious reason why parentheses could change an expression such as this (with two identical operators) is if the operator is right-associative. Unfortunately, there's no official spec that includes ?. and (to my mind) it's difficult to know what associativity would even mean here. Commented Jul 11, 2017 at 12:23
  • @Damien_The_Unbeliever that's a good point. I always was unclear about associativity with this operator as well. Clearly, the chain must execute left-to right but the types somehow seem to be right-to-left?! Commented Jul 11, 2017 at 12:34

4 Answers 4

3

This is because the null-conditional access operator is "short-circuiting". That is, if you write a?.b.c and execute it with a being null, then not only is .b not executed, but .c is also not executed.

As a consequence, when you write a?.b?.c, then the second null-conditional access operator is not about checking whether a or a.b is null. It's only about checking whether a.b is null. So, if a.b can never be null, like in your case, it doesn't make sense to use the null-conditional access operator, which is why the language does not allow it.

This also explains why a?.b?.c and (a?.b)?.c are not the same thing.

For more information about this, you can read the C# Language Design Meeting notes where this was decided.

Sign up to request clarification or add additional context in comments.

1 Comment

2

The type decimal is not a nullable type. Therefore your first expression does not compile. The difference to the second statement is that the inner part of the parenthesis could already be null if c was null. Therefore it compiles.

This could result in a null value: (c?.DecimalField)

The resulting type is System.Nullable<decimal> or short written decimal?

8 Comments

Why do parentheses change the type of the expression at all? That seems unusual.
It makes perfect sense in this case. If c would be null, the type of the expression needs to be a nullable type. But decimal (the type of DecimalField) is not nullable. This means, that the resulting type can't be decimal
c?.DecimalField is of type decimal?. The field does should not matter, the C# language rarely cares about where a value came from. It cares about the type. Here, the type should support ?.. What do you think about that?
@boot4life "Why do parentheses change the type of the expression at all? That seems unusual." That is a very important thing you are about to understand. What you really want looks like this: c?.DecimalField.ToString() When you see it the first time, you may think that it is the same as (c?.DecimalField).ToString() but this is not the case. Suppose c is null. The latter will then call ToString on another null, which gives "" in this case. The former will not call any ToString at all, and simply substitute default(string) which is null of type string.
@JeppeStigNielsen OK, so the whole chain is short-circuited. This is what I was missing. I also now found a draft of the spec which confirms this (github.com/dotnet/csharplang/blob/master/spec/…).
|
0

The decimal type cannot be null, so the null-coalesce operator makes no sense here. Just set toString1 to some value.

In case you would like to compile it in all cases, you should edit your DecimalField which you are comparing to a nullable type by adding decimal ? insted of decimal on your field definition.

Comments

0

You have three variants.

You can set default value, it returns DecimalField or "0":

string toString = c?.DecimalField.ToString() ?? decimal.Zero.ToString();

Without default value, it returns DecimalField or null:

string toString = c?.DecimalField.ToString();

Or you can make DecimalField nullable, it returns DecimalField or null:

public decimal? DecimalField;
...
string toString1 = c?.DecimalField?.ToString(); //Compile now!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.