In C# Why Can't a Conditional Operator Implicitly Cast to a Nullable Type

In C# why can't a conditional operator implicitly cast to a nullable type

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.

Conditional Operator ?: with Nullable type Casting

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.

Nullable types and the ternary operator: why is `? 10 : null` forbidden?

The compiler first tries to evaluate the right-hand expression:

GetBoolValue() ? 10 : null

The 10 is an int literal (not int?) and null is, well, null. There's no implicit conversion between those two hence the error message.

If you change the right-hand expression to one of the following then it compiles because there is an implicit conversion between int? and null (#1) and between int and int? (#2, #3).

GetBoolValue() ? (int?)10 : null    // #1
GetBoolValue() ? 10 : (int?)null // #2
GetBoolValue() ? 10 : default(int?) // #3

Why doesn't the conditional operator correctly allow the use of null for assignment to nullable types?

This doesn't work because the compiler will not insert an implicit conversion on both sides at once, and null requires an implicit conversion to become a nullable type.

Instead, you can write

task.ActualEndDate = TextBoxActualEndDate.Text != "" ? 
DateTime.Parse(TextBoxActualEndDate.Text) : new DateTime?();

This only requires one implicit conversion (DateTime to DateTime?).

Alternatively, you can cast either left side:

task.ActualEndDate = TextBoxActualEndDate.Text != "" ? 
(DateTime?)DateTime.Parse(TextBoxActualEndDate.Text) : null;

This also requires only one implicit conversion.

Nullable type issue with ?: Conditional Operator

The compiler is telling you that it doesn't know how convert null into a DateTime.

The solution is simple:

DateTime? foo;
foo = true ? (DateTime?)null : new DateTime(0);

Note that Nullable<DateTime> can be written DateTime? which will save you a bunch of typing.

Conditional operator cannot cast implicitly?

This is a fairly frequently asked question.

In C#, we almost always reason from inside to outside. When you see

x = y;

we work out what is the type of x, what is the type of y, and whether the type of y is assignment compatible with x. But we do not use the fact that we know what the type of x is when we are working out the type of y.

That's because there might be more than one x:

void M(int x) { }
void M(string x) { }
...
M(y); // y is assigned to either int x or string x depending on the type of y

We need to be able to work out the type of an expression without knowing what it is being assigned to. Type information flows out of an expression, not into an expression.

To work out the type of the conditional expression, we work out the type of the consequence and the alternative expressions, pick the more general of the two types, and that becomes the type of the conditional expression. So in your example, the type of the conditional expression is "int", and it is not a constant (unless the condition expression is constant true or constant false). Since it is not a constant, you can't assign it to byte; the compiler reasons solely from the types, not from the values, when the result is not a constant.

The exception to all these rules is lambda expressions, where type information does flow from the context into the lambda. Getting that logic right was very difficult.

How to use Conditional Operation with Nullable Int

Yes - the compiler can't find an appropriate type for the conditional expression. Ignore the fact that you're assigning it to an int? - the compiler doesn't use that information. So the expression is:

(this.Policy == null) ? null : 1;

What's the type of this expression? The language specification states that it has to be either the type of the second operand or that of the third operand. null has no type, so it would have to be int (the type of the third operand) - but there's no conversion from null to int, so it fails.

Cast either of the operands to int? and it will work, or use another way of expessing the null value - so any of these:

(this.Policy == null) ? (int?) null : 1;

(this.Policy == null) ? null : (int?) 1;

(this.Policy == null) ? default(int?) : 1;

(this.Policy == null) ? new int?() : 1;

I agree it's a slight pain that you have to do this.


From the C# 3.0 language specification section 7.13:

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.

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. Here, "certain good conditions" means certain conversions are possible, which we will get into the details of below.

Now, let's turn to the germane part of the spec:

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.

The issue here is that in

int? number = true ? 5 : null;

only one of the conditional results has a type. Here x is an int literal, and y is null which does not have a type and null is not implicitly convertible to an int1. Therefore, "certain good conditions" aren't met, and a compile-time error occurs.

There are two ways around this:

int? number = true ? (int?)5 : null;

Here we are still in the case where only one of x and y has a type. Note that null still does not have a type yet the compiler won't have any problem with this because (int?)5 and null are both implicitly convertible to int? (§6.1.4 and §6.1.5).

The other way is obviously:

int? number = true ? 5 : (int?)null;

but now we have to read a different clause in the spec to understand why this is okay:

If x has type X and y has type Y then

  • 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.

  • 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.

Here x is of type int and y is of type int?. There is no implicit conversion from int? to int, but there is an implicit conversion from int to int? so the type of the expression is int?.

1: Note further that the type of the left-hand side is ignored in determining the type of the conditional expression, a common source of confusion here.

Implicit conversion from Nullable to normal, any ideas?

It is possible to implement an implicit cast operator, but only to or from types you define. For example, doing something like this..

public class NullableExtensions 
{
public static implicit operator int(int? value)
{
return value ?? default(int);
}
}

.. will return a CS0556 compile error because the cast doesn't include the user-defined type.

The closest you could do is define your own Nullable type that does contain an implicit cast operator:

public struct ImplicitNullable<T> where T: struct
{
public bool HasValue { get { return this._value.HasValue; } }
public T Value { get { return this._value.Value; } }

public ImplicitNullable(T value) : this() { this._value = value; }
public ImplicitNullable(Nullable<T> value) : this() { this._value = value; }

public static implicit operator ImplicitNullable<T>(T value) { return new ImplicitNullable<T>(value); }
public static implicit operator ImplicitNullable<T>(Nullable<T> value) { return new ImplicitNullable<T>(value); }

public static implicit operator T(ImplicitNullable<T> value) { return value._value ?? default(T); }
public static implicit operator Nullable<T>(ImplicitNullable<T> value) { return value._value; }

private Nullable<T> _value { get; set; }

// Should define other Nullable<T> members, especially
// Equals and GetHashCode to avoid boxing
}

Note that although it's possible to write this code, it will likely lead to hard to trace bugs. I would recommend using an explicit cast, or throwing an exception when the value is null.

Afterwards, you can cast to and from as expected:

static void Main()
{
int myInt = 1;
int? nullableInt = 2;

ImplicitNullable<int> implicitInt;

// Convert from int or int?
implicitInt = myInt;
implicitInt = nullableInt;

// Convert to int or int?
myInt = implicitInt;
nullableInt = implicitInt;
}


Related Topics



Leave a reply



Submit