可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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;