C#Ternary运算符评估它何时不应

时间:2016-11-14 07:07:52

标签: c# conditional-operator

这条代码今天引起了我的注意:

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

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

此代码中的例外

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

        throw new InvalidOperationException();
    }

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

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

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

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

2 个答案:

答案 0 :(得分:4)

您忘记了隐式运算符是在编译时确定的。这意味着您拥有的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

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

解决方案很简单:使用明确键入的nulldefault(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>

答案 1 :(得分:-2)

问题不在于三元评估的正确参数,它清楚地不是(尝试它,在隐式运算符中抛出一个不同的异常,代码仍会抛出j因为{{1 }})

所以问题在于隐式演员何时发生。看来:

InvalidOperationException

等同于

((Nullable<byte>)(null)).Value

而不是

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

所以resharper在这里完全错了。