C#三元运算符评估它何时不应该

Sim*_*itt 5 c# conditional-operator

这段代码今天抓住了我:

clientFile.ReviewMonth == null ? null : MonthNames.AllValues[clientFile.ReviewMonth.Value]
Run Code Online (Sandbox Code Playgroud)

clientFile.Review月是一个字节?在失败的情况下,它的值为null.预期的结果类型是字符串.

此代码中的例外情况

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

        throw new InvalidOperationException();
    }
Run Code Online (Sandbox Code Playgroud)

正在评估评估的右侧,然后隐式转换为字符串.

但我的问题是,为什么右手边被评估,显然只应评估左手边?(文档说明"只评估两个表达式中的一个.")

顺便说一句,解决方案是将null转换为字符串 - 这有效但Resharper告诉我演员是多余的(我同意)

编辑:这与"为什么我需要在编译之前添加一个强制转换"类型的三元运算符问题不同.这里的要点是不需要强制转换来使其编译 - 只是为了使其正常工作.

Lua*_*aan 6

您忘记了隐式运算符是在编译时确定的。这意味着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();
  }
}
Run Code Online (Sandbox Code Playgroud)

无效操作不会发生在第三个操作数上,而是发生在第二个操作数上 - null(实际上 a default(LookupCode<object>))不是 type string,因此会调用隐式运算符。隐式运算符抛出无效操作异常。

如果您使用稍微修改的代码段,您可以很容易地看到这是真的:

string result = reviewMonth == null 
                ? default(LookupCode<object>) 
                : "Does this get evaluated?".Dump();
Run Code Online (Sandbox Code Playgroud)

您仍然会收到无效操作异常,并且不会评估第三个操作数。这在生成的 IL 中当然很明显:两个操作数是两个独立的分支;他们俩都没有办法被处决。第一个分支还有另一个明显的痛苦:

ldnull      
call        LookupCode`1.op_Implicit
Run Code Online (Sandbox Code Playgroud)

它甚至没有隐藏在任何地方:)

解决方案很简单:使用显式类型的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>to的隐式转换string,但反之不存在,因此该类型LookupCode<T>优先于string。有趣的是,由于这一切都是在编译时完成的,赋值的 LHS 实际上有所不同:

string result = ... // Fails
var result = ... // Works fine, var is of type LookupCode<object>
Run Code Online (Sandbox Code Playgroud)