Casting with conditional/ternary ("?:") operator - c#

I have this extract of C# source code:
object valueFromDatabase;
decimal result;
valueFromDatabase = DBNull.Value;
result = (decimal)(valueFromDatabase != DBNull.Value ? valueFromDatabase : 0);
result = (valueFromDatabase != DBNull.Value ? (decimal)valueFromDatabase : (decimal)0);
The first result evaluation throws an InvalidCastException whereas the second one does not.
What is the difference between these two?

UPDATE: This question was the subject of my blog on May 27th 2010. Thanks for the great question!
There are a great many very confusing answers here. Let me try to precisely answer your question. Let's simplify this down:
object value = whatever;
bool condition = something;
decimal result = (decimal)(condition ? value : 0);
How does the compiler interpret the last line? The problem faced by the compiler is that the type of the conditional expression must be consistent for both branches; the language rules do not allow you to return object on one branch and int on the other. The choices are object and int. Every int is convertible to object but not every object is convertible to int, so the compiler chooses object. Therefore this is the same as
decimal result = (decimal)(condition ? (object)value : (object)0);
Therefore the zero returned is a boxed int.
You then unbox the int to decimal. It is illegal to unbox a boxed int to decimal. For the reasons why, see my blog article on that subject:
Representation and Identity
Basically, your problem is that you're acting as though the cast to decimal were distributed, like this:
decimal result = condition ? (decimal)value : (decimal)0;
But as we've seen, that is not what
decimal result = (decimal)(condition ? value : 0);
means. That means "make both alternatives into objects and then unbox the resulting object".

The difference is that the compiler can not determine a data type that is a good match between Object and Int32.
You can explicitly cast the int value to object to get the same data type in the second and third operand so that it compiles, but that of couse means that you are boxing and unboxing the value:
result = (decimal)(valueFromDatabase != DBNull.value ? valueFromDatabase : (object)0);
That will compile, but not run. You have to box a decimal value to unbox as a decimal value:
result = (decimal)(valueFromDatabase != DBNull.value ? valueFromDatabase : (object)0M);

The type of the operator will be object and in case the result must be 0 it will be implicitly boxed. But 0 literal is by default has int type so you box int. But with explicit cast to decimal you try to unbox it which is not permitted (boxed type must much with the one you cast back to). That is why you can get exception.
Here is an excerpt from C# Specification:
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 (§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.

Your line should be:
result = valueFromDatabase != DBNull.value ? (decimal)valueFromDatabase : 0m;
0m is the decimal constant for zero
Both parts of a conditional operator should evaluate to the same data type

The x : y part need a common type, the database's value is likely some kind of float and 0 is an int. This happens before the cast to decimal. Try ": 0.0" or ": 0D".

Unless I'm mistaken (which is very possible) its actually the 0 that's causing the exception, and this is down to .NET (crazily) assuming the type of a literal so you need to specify 0m rather than just 0.
See MSDN for more info.

There are two different types for the compiler to decide (at compile time) which one to cast to decimal. This it can't do.

Your answer would work if you combined both:
result = (decimal)(valueFromDatabase != DBNull.Value ? (decimal)valueFromDatabase : (decimal)0);
At least, a similar situation casting into a parameter for me.

Related

Parsing a string to an int difference

I am trying to understand whats happening and why it doesnt work. I was trying to convert "34" to the int 34. I wish to know why there is a difference (and why my version didnt work):
My version:
var cell = GetSpecificString();
return (cell == "") ? null : int.Parse(cell);
SO version:
var cell = GetSpecificString();
if (int.TryParse(cell, out int i)) return i;
return null;
Any tips would be greatly appreciated, as I want to know whats happening here.
Does not work implies here that the Intellisense is complaining because there is no conversion between '' and 'int'. But I want to return a nullable int, so this seems weird to me.
The first version doesn't work because the conditional operator doesn't allow it whereas the second approach uses an if. The rules for the conditional operator are stricter. Both types must be compatible but null and int are not. You could achieve it by using:
return (cell == "") ? (int?)null : int.Parse(cell);
However, i'd really use int.TryParse since an empty string is not the only invalid value:
return int.TryParse(cell, out int parsedValue) ? parsedValue : new int?();
There is already good explanation on stack overflow for this problem:
For complete explanation refer Type of conditional expression cannot be determined because there is no implicit conversion between 'int' and <null>
The spec (§7.14) says that for conditional expression b ? x : y, there are three possibilities, either x and y both have a type and certain good conditions are met, only one of x and y has a type and certain good conditions are met, or a compile-time error occurs.
If only one of x and y has a type, and both x and y are implicitly convertible to that type, then that is the type of the conditional expression.

differences between the conditional operator and an if-else:

I read this question and I found Tim Schmelter's answer:
By the way, that's one of the differences between the conditional operator and an if-else
you can check the answer for this question, and I can't found the reason ?
if conditional operator work like if-else why if else don't need the cast
question :
int? l = lc.HasValue ? (int)lc.Value : null;
"Tim Schmelter" answer :
You have to cast null as well:
int? l = lc.HasValue ? (int)lc.Value : (int?)null;
By the way, that's one of the differences between the conditional operator and an if-else:
if (lc.HasValue)
l = (int)lc.Value;
else
l = null; // works
The literal null alone does not have a type, but it is implicitly convertible to any reference type and to any Nullable<> type. In the expression:
x = null
where x is assigned to null, the compiler can easily infer from the type of the variable (or field or property or parameter or whatever) x what the null literal shall be converted into. For example if x is of type string, the null shall represent the null reference, while if x is of type int?, the null shall represent an instance of Nullable<int> where HasValue is false.
If x is of type int, no implicit conversion exists, and the above expression shall not compile.
(The declaration var x = null; with var is not legal since null has no type in itself.)
On the other hand, in the expression:
someBoolean ? 42 : null /* illegal */
the compiler cannot figure out what type to convert null into. Remember that int is neither reference type, nor Nullable<> type.
If you meant wrapping into a nullable, use:
someBoolean ? (int?)42 : null
or:
someBoolean ? 42 : (int?)null
In both cases the compiler will automatically see that the other operand (on the other side of the colon :) must also be implicitly converted to int?.
If you meant boxing into some base class or interface of int, write that:
someBoolean ? (object)42 : null
or:
someBoolean ? 42 : (object)null
Now, the expressions above could be sub-expressions of a greater containing expression, but the compiler will still need the type of the ?: expression to be clear by itself. For example in:
int? x;
x = someBoolean ? 42 : null; // still illegal!
even if the sub-expression someBoolean ? 42 : null appears inside a larger expression x = someBoolean ? 42 : null where x does have a type, the sub-expression must still acquire its type "intrinsically". The type of x cannot "leak onto" the sub-expression. This "grammar" seems to be a surprise to many new C# developers. Questions like yours are often seen, see e.g. Nullable type issue with ?: Conditional Operator and the threads linked to it.
The if statement doesn't yield a value, so the statements in the "then" and "else" parts don't need to be type compatible in any way.
The conditional operator yields a value, and therefore both parts must be type compatible in some way in order for the compiler to determine the type of the expression.
MSDN says for the conditional operator :
*Either the type of first_expression and second_expression must be the same, or an implicit conversion must exist from one type to the other.*
This is the reason. In the if-else constructor the code in else block is independent from the code in if block so the type-casting can be inferred by the compiler in each case.

Conditional Operator ?: with Nullable type Casting

From the MSDN documentation, the following two snippets are equal:
bool value;
int x = (value) ? 0 : 1;
And
bool value;
int x;
if (value)
x = 0;
else
x = 1;
Great, wonderful. I use it all the time. Terse and effective.
If we try this with a nullable type, like so:
int? x = (value.HasValue) ? value.Value : null;
We get a compile-time error:
The type of conditional expression cannot be determined
because there is no implicit conversion between '{NullableType}' and null.
This compiles fine:
int? value;
int? x;
if (value.HasValue)
x = value.Value;
else
x = null;
So, I understand that the compiler requires an explicit cast in the way of (int?)null to compile the first statement. What I don't understand is why it is required in that statement, but not the If Else block.
null can represent any object-based datatype. You need to cast null as a datatype so it know what you are talking about.
int? x = (value.HasValue) ? value.Value : (int?)null;
I know, it sounds a bit strange.
To answer the questions in the comments:
Why is it not implicit though?
Yeah, I get that. But why do I not have to cast it in a If Else block?
Let's walk through the code.
Your else statement looks like this:
else x = null;
This means you are assigning the value of null to x. This is valid, because x is a int?, which takes nulls.
The difference comes when you have the ternary operator. It says: "assign the value of the operator into x". The question (and the reason for your error) is, what datatype is the result of the ternary operator?
From your code, you can't be sure, and the compiler throws its hands up.
int? x = (value.HasValue) ? value.Value : null;
// int? bool int ??
What datatype is null? You are quick to say "well it's a int?, because the other side is a int and the result is a int?". The problem is, what about the following:
string a = null;
bool? b = null;
SqlConnectionStringBuilder s = null;
This is also valid, which means null can be used for any object-based datatype. This is why you have to explicitly cast null as the type you want to use, because it can be used for anything!
Another explanation (and possible more accurate):
You can't have an implicit cast between a nullable and a non-nullable value.
int is not-nullable (it's a structure), where null is. This is why in Habib's answer you can put the cast on either the left or right side.
For Condtional operator MSDN states:
Either the type of first_expression and second_expression must be
the same, or an implicit conversion must exist from one type to
the other.
So in you case your first_expression and second_expression are:
int? x = (value.HasValue) ? value.Value : null;
^^^^^^^^ ^^^^^
first exp 2nd Exp
Now if you see, your First Expression is of type int, and second expression is null and both are not same and there is no implicit conversion. So casting any of them to `int? solves the problem.
So:
int? x = (value.HasValue) ? (int?) value.Value : null;
Or
int? x = (value.HasValue) ? value.Value : (int?) null;
are fine.
Now why it is not required with if-else, because there multiple statements involved and it not a single statement assigning the value.
var x = value.HasValue ? value.Value : default(int?);
works too.
The documentation for the ?: operator states that the type of the expression b ? x : y is determined by examining the types of x and y:
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.
In your example
int? x = (value.HasValue) ? value.Value : null;
there is no implicit conversion between int and null so the last bullet applies.
The reason is that the type of conditional expression is determined by the second and third operators of the conditional operator (?:) .
Since null has no type, the compiler cannot determine the type of the overall expression so it emits a compiler error.
The reason it works with the simple assignment operator (=) is because the left side of the operator determines the the type. Since in the If statement, the type of x is already known, the compiler doesn't complain.
See section 7.14 (Conditional operator) and section 7.17.1 (Simple assignment) for further explanations.

Why must I cast to (int?) in C# when variable type is [int?]

Can someone please explain me the logic reason why I must cast null to int?
When the left argument type can have their both types ?
Insted of doing
int? k = (DateTime.Now.Ticks%5 > 3 ? 1 : null);
I must do
int? k = (DateTime.Now.Ticks%5 > 3 ? 1 : (int?) null);
although int? k = null is perfectly valid.
An opposite example :
I didn't have to do it in:
string k = (DateTime.Now.Ticks%5 > 3 ? "lala" : null);
int? k = (DateTime.Now.Ticks%5 > 3 ? 1 : (int?) null);
In this case what we have is 1 is int and null is actually null
Now the confusion is the ternary operator is confused as what is the return type int or well null and since its int it won't accept null
So you would need to cast it to a nullable int
Now in the other case you have a string and null is perfectly acceptable for a string
Further explanation can be found at Type inference - Eric
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 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 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.
Because the compiler doesn't use the type of the variable in the left hand side to determine the type of the expression on the right hand side. First it determines what type the expression is, then it determines if it's possible to put it in the variable.
There is no type close enough that is common between an int and a null value. You have to either make the int value nullable or the null value "intable" for the compiler to find a common ground for the values.
When you have a string and a null value the compiler can simply use one of the types, because a string is already nullable.

Type result with conditional operator in C#

I am trying to use the conditional operator, but I am getting hung up on the type it thinks the result should be.
Below is an example that I have contrived to show the issue I am having:
class Program
{
public static void OutputDateTime(DateTime? datetime)
{
Console.WriteLine(datetime);
}
public static bool IsDateTimeHappy(DateTime datetime)
{
if (DateTime.Compare(datetime, DateTime.Parse("1/1")) == 0)
return true;
return false;
}
static void Main(string[] args)
{
DateTime myDateTime = DateTime.Now;
OutputDateTime(IsDateTimeHappy(myDateTime) ? null : myDateTime);
Console.ReadLine(); ^
} |
} |
// This line has the compile issue ---------------+
On the line indicated above, I get the following compile error:
Type of conditional expression cannot be determined because there is no implicit conversion between '< null >' and 'System.DateTime'
I am confused because the parameter is a nullable type (DateTime?). Why does it need to convert at all? If it is null then use that, if it is a date time then use that.
I was under the impression that:
condition ? first_expression : second_expression;
was the same as:
if (condition)
first_expression;
else
second_expression;
Clearly this is not the case. What is the reasoning behind this?
(NOTE: I know that if I make "myDateTime" a nullable DateTime then it will work. But why does it need it?
As I stated earlier this is a contrived example. In my real example "myDateTime" is a data mapped value that cannot be made nullable.)
The compiler does not infer the type of the result of the conditional operator from the usage of the result, but from the types of its arguments. The compiler fails when it sees this expression because it cannot deduce the type of the result:
IsDateTimeHappy(myDateTime) ? null : myDateTime;
Since null and DateTime are not compatible, you need to tell the compiler what the type should be. A cast should do the trick:
DateTime? x = IsDateTimeHappy(myDateTime) ? (DateTime?)null : myDateTime;
OutputDateTime(x);
Now the compiler will have no problems. You can also write the above on one line if you prefer (but I would probably not do this):
OutputDateTime(IsDateTimeHappy(myDateTime) ? (DateTime?)null : myDateTime);
Eric Lippert has a good answer that is also relevant here and goes into more details about how the compiler determines types.
The reason is the ternary operator expects both the operands to be of the same type. The whole operator get worked out BEFORE it is assigned to a result (in this case passed into a function), so the compiler can't know what the result type is.
IsDateTimeHappy(myDateTime) ? null : myDateTime
In the above case there is no conversion path between null and DateTime. As soon as you cast one of them to DateTime?, the compiler can convert the other one:
IsDateTimeHappy(myDateTime) ? (DateTime?)null : myDateTime
//OR
IsDateTimeHappy(myDateTime) ? null : (DateTime?)myDateTime
The fist line of code above works because the compiler can convert DateTime to DateTime? via an implicit conversion operator:
//In Nullable<T>
public static implicit operator T?(T value);
The second line works because null can be assigned to DateTime? since the latter is a reference type.
The implicit conversion is not allowed by the return statement. If you had
if (condition)
return first_expression;
else
return second_expression;
Then you'd be comparing apples to apples. And you'd have no problems - as you stated.
In your case, you're allocated so much space on the stack for a DateTime - which is a non-nullable value type. So you're making a statement that doesn't make any sense to the compiler. If you say, I'm going to pass you an A or a B, then the A and the B need to be the same thing. In your case, the B can never be an A.
What the compiler is saying is:
If IsDateTimeHappy(myDateTime) is false, then I need to return a value of type DateTime equal to myDateTime. If it is true, then I need to return a value equal to null, but you haven't told me what type it should be!
That's why Mark's answer is the solution. After you provide a cast telling the compiler what type of value will be returned if the condition is true, it can check whether the true and false return values can be converted to (or are of) the same type.
Cheers Mark! ;-)
Instead of null use default(DateTime?) and then both sides of the ternary will have compatible types.

Categories

Resources