C# null-conditional operator允许有用的短路:
double? range = (unit as RangedUnit)?.WeaponRange;
不幸的是,空条件运算符不能以相同的方式用于简写赋值,因为它返回一个值(不能用于左手赋值):
(unit as RangedUnit)?.PreferredTarget = UnitType.Melee;
导致可能的替代语法:
if (unit is RangedUnit)
{
(unit as RangedUnit).PreferredTarget = UnitType.Melee;
}
如果编译器知道RangedUnit是引用类型(不是值类型),为什么它不能有条件地执行简写语法
refTypeInstance?.SomeField = value;
(即如果 refTypeInstance 为空,则什么也不做。如果 refTypeInstance 不为空,则执行该语句)
更新(结论):
- 空条件运算符不能用在赋值语句的左侧,因为这会违反赋值语句表达式树的预期求值逻辑(短路赋值操作而不执行它)
- 理想的解决方案是一个新的条件赋值运算符(仅当赋值的左侧不为空时才执行),本质上是“如果不为空,一个赋值一个衬里”
您期望的行为:
(i.e. If refTypeInstance is null, then simply do nothing. If refTypeInstance is not null, then execute the statement)
由于运营商的工作方式,这是不可能的。更具体地说,您对运算符优先级以及如何基于此形成表达式树有疑问:
在声明中
(unit as RangeUnit).PreferredTarget = UnitType.Melee;
赋值运算符 (=
) 位于表达式树的根部,左右表达式为分支。
NullReferenceException
会在评估左手时(赋值之前)发生。此时编译器已经开始计算=
。由于取消引用运算符 (.
) 将在运行时抛出 NullReferenceException
,因此编译器继续解析表达式树是安全的。
另一方面,如果允许此语句:
(unit as RangeUnit)?.PreferredTarget = UnitType.Melee;
...编译器必须发出代码来检查 refTypeInstance
的值是否为 null。它可以做到这一点,但问题是,编译器将如何处理它当前正在处理的表达式树?它不能像第一个示例那样简单地继续,因为它必须丢弃表达式树上的 =
和树下的 .
。它基本上必须插入两种解析树的选择,一种是 ?.
的左边是 null
,另一种不是。然而,这将改变控制流,这绝对不是您对运算符(operator)的期望。
或者换句话说:当 ?.
只是将运算符的计算短路到表达式树的分支下时,您会认为这是预期的行为。但在这种情况下,这会改变表达式树中更高层运算符的行为,这是您绝对不会想到的。