views:

1982

answers:

4

The following code will not compile:

string foo = "bar";
Object o = foo == null ? DBNull.Value : foo;

I get: Error 1 Type of conditional expression cannot be determined because there is no implicit conversion between 'System.DBNull' and 'string'

To fix this, I must do something like this:

string foo = "bar";
Object o = foo == null ? DBNull.Value : (Object)foo;

This cast seems pointless as this is certainly legal:

string foo = "bar";
Object o = foo == null ? "gork" : foo;

It seems to me that when the ternary branches are of different types, the compiler will not autobox the values to the type object...but when they are of the same type then the autoboxing is automatic.

In my mind the first statement should be legal...

Can anyone describe why the compiler does not allow this and why the designers of C# chose to do this? I believe this is legal in Java...Though I have not verified this.

Thanks.

EDIT: I am asking for an understanding of why Java and C# handle this differently, what is going on underneath the scenes in C# that make this invalid. I know how to use ternary, and am not looking for a "better way" to code the examples. I understand the rules of ternary in C#, but I want to know WHY...

EDIT (Jon Skeet): Removed "autoboxing" tag as no boxing is involved in this question.

+10  A: 

DBNull.Value returns type DBNull.

You want the type to be string.

While string can be null it cannot be DBNull.

In your code the statement on the right of the equals executes before assignment to the object.

Basically if you use:

[condition] ? true value : false value;

In .Net both the true and false options need to be implicitly convertible to the same type, before whatever you assign them to.

This is a result of how C# deals with type-safety. For instance the following is valid:

string item = "item";

var test = item != null ? item : "BLANK";

C# doesn't support dynamic types, so what is test? In C# every assignment is also a statement with a return value, so although the var construct is new in C#3 the statement on the right of the equals always has to resolve to a single type.

Keith
You do not understand the question. I want the type object, not string. I know how ternary works. I am asking why the compiler can't auto box two different types in the ternary to a object.
mmattax
I think he gave the correct answer, most notably he this part: "In .Net both the true and false options need to be the same type, before whatever you assign them to.This is a result of how C# deals with type-safety."
Evan Teran
@Keith: They don't actually have to be the same type. Just one type implicitly convertible t the other.@mmattax: There's no boxing going on here at all. Neither DBNull nor String are value types.
Jon Skeet
Boxing indeed happens as the value types are converted into an object type. and I am not asking about the rules...I want to know why this was made to happen...
mmattax
mmattax: There are no value types involved, so there's no boxing. It helps to be precise when using terminology. (It's not called the ternary operator either - it's the conditional operator. It happens to be *a* ternary operator - and the only one in C# - but that's not its name.)
Jon Skeet
@Keith - but I want to resolve to a single type: object, therefore there needs to be no dynamic types involved.
mmattax
Thanks Jon for the clarification. mmattax - in .Net strings are immutable reference types - they aren't boxed. I've only used the implicit var keyword to try and help illustrate the point.
Keith
+38  A: 

The compiler requires that either the types of second and third operands are the same, or that one is implicitly convertible to the other. In your case, the types are DBNull and string, neither of which is implicitly convertible to the other. Casting either of them to object solves that.

EDIT: Looks like it is indeed legal in Java. Quite how it works out what to do when it comes to method overloading, I'm not sure... I've just looked at the JLS, and it's extremely unclear about what the type of the conditional is when there are two incompatible reference types involved. The C# way of working may be more irritating occasionally, but it's clearer IMO.

The relevant section of the C# 3.0 spec is 7.13, the conditional operator:

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
  • 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.
Jon Skeet
+4  A: 

By the way, your code is a special case that doesn't have to use the conditional operator at all. Instead, the null coalesce operator is more appropriate (but still requires casting):

object result = (object)foo ?? DBNull.Value;
Konrad Rudolph
+2  A: 

The other answers explain it well. One extra tidbit:

Boxing would not be involved here even if this did compile. String and DBNull are both reference (class) types, not value types, so a string can be stored in an object variable without boxing.

Neil Whitaker