您忘记了隐式运算符是在编译时确定的。这意味着您拥有的null 实际上是LookupCode<T> 类型(由于类型推断在三元运算符中的工作方式),并且需要使用隐式运算符将其转换为字符串;这就是你的例外。
void Main()
{
byte? reviewMonth = null;
string result = reviewMonth == null
? null // Exception here, though it's not easy to tell
: new LookupCode<object> { Description = "Hi!" };
result.Dump();
}
class LookupCode<T>
{
public string Description { get; set; }
public static implicit operator string(LookupCode<T> code)
{
if (code != null) return code.Description;
throw new InvalidOperationException();
}
}
无效操作不会发生在第三个操作数上,而是发生在第二个操作数上 - null(实际上是 default(LookupCode<object>))不是 string 类型,因此调用了隐式运算符。隐式操作符抛出无效操作异常。
如果您使用稍作修改的代码,您可以很容易地看出这是真的:
string result = reviewMonth == null
? default(LookupCode<object>)
: "Does this get evaluated?".Dump();
你仍然得到一个无效的操作异常,并且第三个操作数没有被计算。这在生成的 IL 中当然非常明显:两个操作数是两个独立的分支;他们两个都没有办法被处决。而第一个分支还有另一个令人痛苦的显而易见的事情:
ldnull
call LookupCode`1.op_Implicit
它甚至没有隐藏在任何地方:)
解决方案很简单:使用显式类型的null、default(string)。 R# 完全是错误的 - 在这种情况下,(string)null 与 null 不同,并且 R# 在这种情况下具有错误的类型推断。
当然,这在 C# 规范(14.13 - 条件运算符)中都有描述:
?: 运算符的第二个和第三个操作数控制条件表达式的类型。
让 X 和 Y
是第二个和第三个操作数的类型。那么,
- 如果 X 和 Y 是同一类型,则这是条件表达式的类型。
- 否则,如果存在从 X 到 Y 的隐式转换(第 13.1 节),而不是从 Y 到 X,则 Y 是
条件表达式。
- 否则,如果存在从 Y 到 X,但不存在从 X 到 Y 的隐式转换(第 13.1 节),则 X 是
条件表达式。
- 否则无法确定表达式类型,并出现编译时错误。
在您的情况下,存在从LookupCode<T> 到string 的隐式转换,但反之亦然,因此LookupCode<T> 类型优于string。有趣的是,由于这一切都是在编译时完成的,因此赋值的 LHS 实际上有所作为:
string result = ... // Fails
var result = ... // Works fine, var is of type LookupCode<object>