null conditional operator not working with nullabl

2019-01-23 10:51发布

问题:

I'm writing a piece of code in c#6 and for some strange reason this works

var value = objectThatMayBeNull?.property;

but this doesn't:

int value = nullableInt?.Value;

By not works I mean I get a compile error saying Cannot resolve symbol 'Value'. Any idea why the null conditional operator ?. isn't working?

回答1:

Okay, I have done some thinking and testing. This is what happens:

int value = nullableInt?.Value;

Gives this error message when compiling:

Type 'int' does not contain a definition for `Value'

That means that ? 'converts' the int? into the actual int value. This is effectively the same as:

int value = nullableInt ?? default(int);

The result is an integer, which doesn't have a Value, obviously.

Okay, might this help?

int value = nullableInt?;

No, that syntax isn't allowed.

So what then? Just keep using .GetValueOrDefault() for this case.

int value = nullableInt.GetValueOrDefault();


回答2:

The reason for this is that accessing the value with a null conditional operator would be pointless:

  • When you apply x?.p where p is a non-nullable value type T, the result is of type T?. By the same token, the result of nullableInt?.Value operation must be nullable.
  • When your Nullable<T> has a value, the result of nullableInt?.Value would be the same as the value itself
  • When your Nullable<T> does not have a value, the result would be null, which is, again, the same as the value itself.

Although it does not make sense to access Value with the ?. operator, it does make sense to access other properties of nullable value types. The operator works consistently with nullable value types and with reference types, so these two implementations produce identical behavior:

class PointClass {
    public int X { get; }
    public int Y { get; }
    public PointClass(int x, int y) { X = x; Y = y; }
}
struct PointStruct {
    public int X { get; }
    public int Y { get; }
    public PointStruct(int x, int y) { X = x; Y = y; }
}
...
PointClass pc = ...
PointStruct? ps = ...
int? x = pc?.X;
int? y = ps?.Y;

In case of a nullable struct the operator lets you access a property of the underlying type PointStruct, and it adds nullability to the result in the same way that it does for non-nullable properties of the reference type PointClass.



回答3:

With regards to nullable types, the ?. operator is saying if not null, use the wrapped value. So for a nullable int, if the nullable has value 8, the result of ?. would be 8, not the nullable that contains 8. Since Value is not a property of an int, you get an error.

So, the example of trying to use property Value quite rightly fails, but the following would work,

var x = nullableInt?.ToString();

Consider the null-coalescing operator, ??.

var x = nullableInt ?? 0;

Here, the operator says, if null, return 0, otherwise return the value inside the nullable, which in this case is an int. The ?. operator is performing similarly with regards to extracting the content of the nullable.

For your specific example, you should use the ?? operator and an appropriate default rather than the ?. operator.



回答4:

I basically agree with the other answers. I was just hoping that the observed behavior could be backed up by some form of authoritative documentation.

Since I can't find the C# 6.0 specification anywhere (is it out yet?), the closest I found to "documentation" are the C# Language Design Notes for Feb 3, 2014. Assuming the information found in there still reflects the current state of affairs, here are the relevant parts that formally explain the observed behavior.

The semantics are like applying the ternary operator to a null equality check, a null literal and a non-question-marked application of the operator, except that the expression is evaluated only once:

e?.m(…)   =>   ((e == null) ? null : e0.m(…))
e?.x      =>   ((e == null) ? null : e0.x)
e?.$x     =>   ((e == null) ? null : e0.$x)
e?[…]     =>   ((e == null) ? null : e0[…])

Where e0 is the same as e, except if e is of a nullable value type, in which case e0 is e.Value.

Applying that last rule to:

nullableInt?.Value

... the semantically equivalent expression becomes:

((nullableInt == null) ? null : nullableInt.Value.Value)

Clearly, nullableInt.Value.Value can't compile, and that's what you observed.

As to why the design decision was made to apply that special rule to nullable types specifically, I think dasblinkenlight's answer covers that nicely, so I won't repeat it here.


Additionally, I should mention that, even if, hypothetically, we didn't have this special rule for nullable types, and the expression nullableInt?.Value did compile and behave as you originally thought...

// let's pretend that it actually gets converted to this...
((nullableInt == null) ? null : nullableInt.Value)

still, the following statement from your question would be invalid and produce a compilation error:

int value = nullableInt?.Value; // still would not compile

The reason why it would still not work is because the type of the nullableInt?.Value expression would be int?, not int. So you would need to change the type of the value variable to int?.

This is also formally covered in the C# Language Design Notes for Feb 3, 2014:

The type of the result depends on the type T of the right hand side of the underlying operator:

  • If T is (known to be) a reference type, the type of the expression is T
  • If T is (known to be) a non-nullable value type, the type of the expression is T?
  • If T is (known to be) a nullable value type, the type of the expression is T
  • Otherwise (i.e. if it is not known whether T is a reference or value type) the expression is a compile time error.

But if you would then be forced to write the following to make it compile:

int? value = nullableInt?.Value;

... then it seems pretty pointless, and it wouldn't be any different from simply doing:

int? value = nullableInt;

As others have pointed out, in your case, you probably meant to use the null-coalescing operator ?? all along, not the null-conditional operator ?..



回答5:

The null conditional operator also unwraps the nullable variable. So after the "?." operator, the “Value” property is no longer needed.

I wrote a post that goes more into detail on how I came across this. In case you're wondering

http://www.ninjacrab.com/2016/09/11/c-how-the-null-conditional-operator-works-with-nullable-types/



回答6:

Simply because (based on sstan's answer above)

var value = objectThatMayBeNull?.property;

is evaluated by compiler like

var value = (objectThatMayBeNull == null) ? null : objectThatMayBeNull.property

and

int value = nullableInt?.Value;

like

int value = (nullableInt == null) ? null : nullableInt.Value.Value;

when nullableInt.Value.Value is Cannot resolve symbol 'Value' syntax error!



回答7:

int doesn't have a Value property.

Consider:

var value = obj?.Property

Is equivalent to:

value = obj == null ? null : obj.Property;

That makes no sense with int and hence not with int? via ?.

The old GetValueOrDefault() does though make sense with int?.

Or for that matter, since ? has to return something nullable, simply:

int? value = nullableInt;