views:

136

answers:

5

I'm working with a nullable DateTime object and ran into some strange behavior. Here's a sample function:

    public DateTime? Weird()
    {
        DateTime check = DateTime.Now;
        DateTime? dt;
        if (check == DateTime.MinValue)
            dt = null;
        else
            dt = Viewer.ActiveThroughUTC.ToLocalTime();

        //this line give a compile error
        dt = (check == DateTime.MinValue) ? (null) : (Viewer.ActiveThroughUTC.ToLocalTime());
        return dt;
    }

As far as I know, the line with the ternary operator should be the same as the preceding four lines, but VS2010 is giving me a compile error, saying that no conversion exists between <null> and DateTime (even though the object in question is a 'DateTime?'). Is there something I should know about the ternary operator or is this (gasp?) a bug?

+8  A: 

Both elements in the ?: operator should be of the same type (but don't have to be - see the details below). Cast null to DateTime?:

dt = (check == DateTime.MinValue) ? (DateTime?)null : ...

From the spec:

The second and third operands of the ?: operator control the type of the conditional expression. Let X and Y be the types of the second and third operands. Then,

If X and Y are the same type, then this is the type of the conditional expression.

  • Otherwise, if an implicit conversion (Section 6.1) exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.
  • Otherwise, if an implicit conversion (Section 6.1) exists from Y to X, but not from X to Y, then X is the type of the conditional expression.
  • Otherwise, no expression type can be determined, and a compile-time error occurs.

(Interestingly, it's not actually called the "ternary" operator. It's one possible ternary (three-valued) operator, and I'm not aware of any others in C#. It's called the "?:" operator, which is a little hard to pronounce. Also called the "conditional" operator.)

Michael Petrotta
A nitpick: Your first sentence contradicts the quote from the spec. You say *"both elements ... must of the same type"*, but the spec also explains how a conversion is attempted when they're *not* of the same type.
LukeH
@LukeH: yup, thus my prevarication ("but see below for details"). Maybe I'll rephrase it a little.
Michael Petrotta
This, along with the other answer that quoted the spec, pointed out something that I think is worth stating explicitly. In a statement like A=T:X?Y, the type of A has no bearing whatsoever on the validity of the expression. Since (in my case) X was a generic null and Y was a DateTime (and specifically not a nullable DateTime), the expression type couldn't be determined, even though A would have accepted either value. Thanks.
Raumornie
A: 

A conditional statement's return options must be of the same type (or only one most be implicitly convertable), the implicit conversion to the nullable that the compiler infers with:

dt = null;

Doesn't happen here (if they differ should the first be converted to the second? or vice-versa?), so your return types from each option need to match or be convertable. For example this would work:

dt = check == DateTime.MinValue ? (DateTime?)null : Viewer.ActiveThroughUTC.ToLocalTime();
Nick Craver
The return options do not have to be of the same type. They can be very different types as long as one is implicitly convertible to the other.
JaredPar
@JaredPar - That's not completely accurate either, if a both can be converted you'll also get the error, I'll update to make it a bit clearer.
Nick Craver
A: 

Using the ternery operator the arguments have to be the same type. Change it to:

dt = (check == DateTime.MinValue) ? (DateTime?)null : 
        (DateTime?)Viewer.ActiveThroughUTC.ToLocalTime();   

the cast on the second argument (the real dateime) may not be necessary as there is an implicit cast fropm DateTime to DateTime?, and the first cast (DateTime?)null tells the compiler what to cast it to...

Charles Bretana
A: 

Use new DateTime?() instread of null. That way it knows what type the expression is supposed to be.

dt = check == DateTime.MinValue ? new DateTime?() : DateTime.Now;
Jonathan Allen
Or even default(DateTime?) to make it more obvious what you're doing.
Andrew Kennan
+2  A: 

Several answers have incorrectly stated that both values of the conditional operator must be the same type. This is decidedly untrue and is covered in detail in section 7.13 of the language spec

From the spec (X and Y are the types of the two values)

  • If X and Y are the same type, then this is the type of the conditional expression.
  • Otherwise, if an implicit conversion (§6.1) exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.
  • Otherwise, if an implicit conversion (§6.1) exists from Y to X, but not from X to Y, then X is the type of the conditional expression.
  • Otherwise, no expression type can be determined, and a compile-time error occurs.

The second and third cases allow for the types to be different so long as there is an implicit conversion from one to the other (but not back).

The easiest way to fix this scenario is to explicitly cast one of the operands to DateTime?

DateTime? dt = (check == DateTime.MinValue) 
  ? (DateTime?)null
  : Viewer.ActiveThroughUTC.ToLocalTime();
JaredPar