c#三元运算符在不应该't时求值

本文关键字:不应该 运算符 三元 | 更新日期: 2023-09-27 18:17:57

今天这段代码把我难住了:

clientFile.ReviewMonth == null ? null : MonthNames.AllValues[clientFile.ReviewMonth.Value]

clientFile。评论月是一个字节吗?在失败的情况下,它的值为空。期望的结果类型是字符串。

异常在以下代码

    public static implicit operator string(LookupCode<T> code)
    {
        if (code != null) return code.Description;
        throw new InvalidOperationException();
    }

正在对求值的右侧求值,然后隐式地转换为字符串。

但我的问题是,为什么只计算左边的值而计算右边呢?(文档声明"两个表达式中只计算一个。")

顺便说一下,解决方案是将null强制转换为string -这是有效的,但Resharper告诉我强制转换是多余的(我同意) 编辑:这与"为什么我需要在编译之前添加强制转换"类型三元运算符问题不同。这里的要点是,不需要强制转换就可以使其编译—只需使其正确工作即可。

c#三元运算符在不应该't时求值

您忘记了隐式操作符是在编译时确定的。这意味着您拥有的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)nullnull不一样,r#在这种情况下有错误的类型推断。

当然,这些都在c#规范(14.13 -条件运算符)中有描述:

?:操作符的第二个和第三个操作数控制条件表达式的类型。

设X和Y是第二个和第三个操作数的类型。然后,

  • 如果X和Y是相同的类型,那么这就是条件表达式的类型。
  • 否则,如果存在从X到Y的隐式转换(§13.1),但不存在从Y到X的隐式转换,则Y是的类型
  • 否则,如果存在从Y到X的隐式转换(§13.1),但不存在从X到Y的隐式转换,则X是的类型
  • 否则,无法确定表达式类型,并发生编译时错误。

在您的示例中,存在从LookupCode<T>string的隐式转换,而不是相反,因此类型LookupCode<T>优先于string。有趣的是,由于这些都是在编译时完成的,赋值的LHS实际上是有区别的:

string result = ... // Fails
var result = ... // Works fine, var is of type LookupCode<object>

问题不是关于三进制求值的正确参数,它显然不是(尝试一下,在隐式操作符中抛出不同的异常,代码仍然会抛出InvalidOperationException,因为((Nullable<byte>)(null)).Value)

所以问题是隐式强制转换何时发生。似乎:

clientFile.ReviewMonth == null ? null : MonthNames.AllValues[clientFile.ReviewMonth.Value]

等于

(string)(clientFile.ReviewMonth == null ? (Nullable<LookupCode<byte>>)null : (Nullable<LookupCode<byte>>)MonthNames.AllValues[clientFile.ReviewMonth.Value]);

而不是

clientFile.ReviewMonth == null ? (string)null : (string)MonthNames.AllValues[clientFile.ReviewMonth.Value]);

所以resharper在这里是完全错误的