Simon Hewitt Simon Hewitt - 21 days ago 10
C# Question

C# Ternary Operator evaluating when it shouldn't

This piece of code caught me out today:

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


clientFile.Review month is a byte? and its value, in the failing case, is null.
The expected result type is string.

The exception is in this code

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

throw new InvalidOperationException();
}


The right hand side of the evaluation is being evaluated and then implicitly converted to a string.

But my question is why is the right hand side being evaluated at all when clearly only the left hand side should be evaluated?
(The documentation states that "Only one of the two expressions is evaluated.")

The solution incidentally is to cast the null to string - that works but Resharper tells me that the cast is redundant (and I agree)

Edit: This is different to the "Why do I need to add a cast before it will compile" type ternary operator question. The point here is that no cast is required to make it compile - only to make it work correctly.

Answer

You're forgetting that implicit operators are determined at compile-time. That means that the null you have is actually of type LookupCode<T> (due to the way type inference works in a ternary operator), and needs to be cast using the implicit operator to a string; that's what gives you your exception.

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();
  }
}

The invalid operation doesn't happen on the third operand, it happens on the second - the null (actually a default(LookupCode<object>)) isn't of type string, so the implicit operator is invoked. The implicit operator throws an invalid operation exception.

You can easily see this is true if you use a slightly modified piece of code:

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

You still get an invalid operation exception, and the third operand isn't evaluated. This is of course painfully obvious in the generated IL: the two operands are two separate branches; there's no way for them both to be executed. And the first branch has another painfully obvious thing:

ldnull      
call        LookupCode`1.op_Implicit

It's not even hidden anywhere :)

The solution is simple: use an explicitly typed null, default(string). R# is simply wrong - (string)null isn't the same as null in this case, and R# has wrong type inference in this scenario.

Comments