51

Following expression is ok

short d = ("obj" == "obj" ) ? 1 : 2;

But when you use it like below, syntax error occurs

short d = (DateTime.Now == DateTime.Now) ? 1 : 2;

Cannot implicitly convert type 'int' to 'short'. An explicit conversion exists (are you missing a cast?)

Can anyone explain why this is so?

Is there a difference between comparing string-to-string and datetime-to-datetime in a ternary operator, why?

I would be grateful if you could help me.

svick
  • 236,525
  • 50
  • 385
  • 514
Mehmet Kasalak
  • 457
  • 4
  • 13
  • 5
    from [tag:ternary-operator]: "A ternary operator is **any** operator that takes three arguments. For the ternary conditional operator `?`...`:`, use `tag:conditional-operator`" (My emphasis) – Damien_The_Unbeliever Feb 14 '14 at 13:52
  • 2
    a tailor-made question for [Eric Lippert](http://stackoverflow.com/users/88656/eric-lippert) – Habib Feb 14 '14 at 13:55
  • 2
    TRWTF is that `d` usually will be equal to `2` in the second case. – Joker_vD Feb 14 '14 at 14:00
  • @Joker_vD: Are you sure? DateTime implements a true equality operator (i.e., not identity equality) and DateTime.Now has a resolution of about 10ms. I'd be surprised if you managed to get that statement to evaluate to false in anything but the raciest of conditions. – Phoshi Feb 14 '14 at 16:54
  • @Phoshi Well, on my home machine it's indeed is equal to `1` consistently. However, on the computer I use at work, the result is fifty-fifty: sommetimes it's `1`, sometimes is `2`. – Joker_vD Feb 14 '14 at 17:27

3 Answers3

59

C# language specification, version 5, section 6.1.9:

An implicit constant expression conversion permits the following conversions:

  • A constant-expression (§7.19) of type int can be converted to type sbyte, byte, short, ushort, uint, or ulong, provided the value of the constant-expression is within the range of the destination type.

Your first example is a constant expression, because it can be evaluated at compile time. But see section 7.19 for more details:

Only the following constructs are permitted in constant expressions:

  • Literals (including the null literal).

[...]

  • The predefined +, –, *, /, %, <<, >>, &, |, ^, &&, ||, ==, !=, <, >, <=, and >= binary operators, provided each operand is of a type listed above.
  • The ?: conditional operator.
Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • This is the right answer, +1 – ken2k Feb 14 '14 at 13:59
  • Spot on. The kind of answer we should see more of on SO! – Baldrick Feb 14 '14 at 14:00
  • I may sound to rigorous, but string literals realy shouldn't be treated as constant expressions. Why is `System.String` special? – Joker_vD Feb 14 '14 at 14:01
  • 8
    @Joker_vD - care to explain your reasoning? Why should string *literals* be treated any differently from any other literals? – Damien_The_Unbeliever Feb 14 '14 at 14:03
  • @Damien_The_Unbeliever Because `System.String` is a reference type. All literals are value types, *except* strings. Why am I disallowed to have, say, `Point { x: 0.0, y: 0.0 }` literal? To have `DateTime { totalMilliseconds: 12 }` literal? – Joker_vD Feb 14 '14 at 14:11
  • @Joker_vD Isn't your own example convincing enough that whether the type is a value type is irrelevant? –  Feb 14 '14 at 14:16
  • @Joker_vD - so your complaint is more along the lines that there are string literals but no means to create new types of literals? – Damien_The_Unbeliever Feb 14 '14 at 14:16
  • @Damien_The_Unbeliever "Either any of the reference types shouldn't have literals, or either all value types should have literals". Right now, `string` is so more equal than other types that it ticks me off. That's why I said that it "may sound too rigorous". – Joker_vD Feb 14 '14 at 14:21
  • 9
    @Joker_vD: If your question "why is string special?" is not rhetorical then the answer is: strings are special for the same reason that "32 bit int" is special: because it is **highly pragmatic** to put support for these commonly used data types directly into the language. C# was not designed to be democratic/agnostic/bring-balance-to-the-Force/whatever with respect to its treatment of data types. The data types that in practice people use in LOB applications are special because it is pragmatic to make them special. – Eric Lippert Feb 14 '14 at 18:15
17

I believe in the first case the compiler knows that the strings are equal at compile time and therefore optimizes the code to just:

short d = 1;

That works because 1 can be assigned to short variable.

In the second case optimization cannot happen because compiler cannot infer equality at compile time, so it leaves:

short d = (DateTime.Now == DateTime.Now) ? (long)1 : (long)2;

This will compile:

short d = (DateTime.Now == DateTime.Now) ? (short)1 : (short)2;

IL (LinqPad) for call short d = ("obj" == "obj" ) ? 1 : 2;:

IL_0001:  ldc.i4.1    
IL_0002:  stloc.0     // d
Jakub Konecki
  • 45,581
  • 7
  • 87
  • 126
4

"obj" == "obj" can be resolved at compile time; compiler treats it as

short d = 1;

namespace ConsoleApplication1 {
  class Program {
    static void Main(string[] args) {
      short d = ("obj" == "obj") ? 1 : 2;
    }
  }
}

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
 // Code size       4 (0x4)
 .maxstack  1
 .locals init ([0] int16 d)
 IL_0000:  nop
 IL_0001:  ldc.i4.1
 IL_0002:  stloc.0
 IL_0003:  ret
 } // end of method Program::Main

DateTime.Now == DateTime.Now can't be resolved at compile time and throws an error.

Ondrej Svejdar
  • 21,349
  • 5
  • 54
  • 89
  • 5
    That's obvious, and it does not explain error – Sergey Berezovskiy Feb 14 '14 at 13:52
  • @SergeyBerezovskiy On the contrary? It explains why there is no error in the first case—even though obviously it should be a compile error. – Joker_vD Feb 14 '14 at 13:54
  • 1
    @Joker_vD No, it explains why there is no error in the first case, and why there shouldn't be one. –  Feb 14 '14 at 13:54
  • @Joker_vD why compiler treats ternary operator differently? – Sergey Berezovskiy Feb 14 '14 at 13:54
  • 1
    @SergeyBerezovskiy C# allows an implicit conversion from `int` to `short` if the value is constant and fits in `short`. In the first case, the value is constant. In the second case, the value is not constant. In other words, the fact that `"obj" == "obj"` can be resolved at compile time is why `("obj" == "obj" ) ? 1 : 2` can be resolved at compile time, which is why it is valid for initialising a `short`. –  Feb 14 '14 at 13:55
  • 1
    @SergeyBerezovskiy is right, it obviously explains the difference between the two cases, but it doesn't explain why the **result** is different. – ken2k Feb 14 '14 at 13:55
  • I want a simple explanation for the answer. I couldn't understand the IL code – Mehmet Kasalak Feb 14 '14 at 14:16
  • @ken2k: Yes, it does. C# allows implicit int->short only for values known at compile-time. "obj"=="obj" is known at compile-time so the entire expression can be optimised out, however DateTime.Now==DateTime.Now is not constant nor known at compiletime therefore the assignment is done at runtime therefore it is illegal to implicitly convert it. The C# compiler could probably look down both paths and see there are constant values that are both safely castable, but it does not. – Phoshi Feb 14 '14 at 16:57