Reputation: 25409
This code compiles:
private static void Main(string[] args)
{
bool? fred = true;
if (fred == true)
Console.WriteLine("fred is true");
else if (fred == false)
Console.WriteLine("fred is false");
else Console.WriteLine("fred is null");
}
This code does not compile.
private static void Main(string[] args)
{
bool? fred = true;
if (fred)
Console.WriteLine("fred is true");
else if (!fred)
Console.WriteLine("fred is false");
else Console.WriteLine("fred is null");
}
I thought if(booleanExpression == true)
was supposed to be a redundancy. Why isn't it in this case?
Upvotes: 36
Views: 6961
Reputation: 146499
Because fred is not a boolean. it is a struct, which has a boolean property called IsNull, or HasValue, or whatever... The object named fred is the complex composite object containing a boolean and a value, not a primitive boolean itself...
Below, for example is how a Nullable Int could be implemented. The generic Nullable is almost certainly implemented similarly (but generically). You can see here how the implicit and explicit conversions are implemented..
public struct DBInt
{
// The Null member represents an unknown DBInt value.
public static readonly DBInt Null = new DBInt();
// When the defined field is true, this DBInt represents a known value
// which is stored in the value field. When the defined field is false,
// this DBInt represents an unknown value, and the value field is 0.
int value;
bool defined;
// Private instance constructor. Creates a DBInt with a known value.
DBInt(int value)
{
this.value = value;
this.defined = true;
}
// The IsNull property is true if this DBInt represents an unknown value.
public bool IsNull { get { return !defined; } }
// The Value property is the known value of this DBInt, or 0 if this
// DBInt represents an unknown value.
public int Value { get { return value; } }
// Implicit conversion from int to DBInt.
public static implicit operator DBInt(int x)
{ return new DBInt(x); }
// Explicit conversion from DBInt to int. Throws an exception if the
// given DBInt represents an unknown value.
public static explicit operator int(DBInt x)
{
if (!x.defined) throw new InvalidOperationException();
return x.value;
}
public static DBInt operator +(DBInt x)
{ return x; }
public static DBInt operator -(DBInt x)
{ return x.defined? -x.value: Null; }
public static DBInt operator +(DBInt x, DBInt y)
{
return x.defined && y.defined?
x.value + y.value: Null;
}
public static DBInt operator -(DBInt x, DBInt y)
{
return x.defined && y.defined?
x.value - y.value: Null;
}
public static DBInt operator *(DBInt x, DBInt y)
{
return x.defined && y.defined?
x.value * y.value: Null;
}
public static DBInt operator /(DBInt x, DBInt y)
{
return x.defined && y.defined?
x.value / y.value: Null;
}
public static DBInt operator %(DBInt x, DBInt y)
{
return x.defined && y.defined?
x.value % y.value: Null;
}
public static DBBool operator ==(DBInt x, DBInt y)
{
return x.defined && y.defined?
x.value == y.value: DBBool.Null;
}
public static DBBool operator !=(DBInt x, DBInt y)
{
return x.defined && y.defined?
x.value != y.value: DBBool.Null;
}
public static DBBool operator >(DBInt x, DBInt y)
{
return x.defined && y.defined?
x.value > y.value: DBBool.Null;
}
public static DBBool operator <(DBInt x, DBInt y)
{
return x.defined && y.defined?
x.value < y.value: DBBool.Null;
}
public static DBBool operator >=(DBInt x, DBInt y)
{
return x.defined && y.defined?
x.value >= y.value: DBBool.Null;
}
public static DBBool operator <=(DBInt x, DBInt y)
{
return x.defined && y.defined?
x.value <= y.value: DBBool.Null;
}
public override bool Equals(object o)
{
try { return (bool) (this == (DBInt) o); }
catch { return false; }
}
public override int GetHashCode()
{ return (defined)? value: 0; }
public override string ToString()
{ return (defined)? .ToString(): "DBInt.Null"; }
}
Upvotes: 8
Reputation: 2859
The implementation issue is perfectly stated by saying: Fred
is of type Nullable<bool>
and the !
operator is not defined for Nullable<bool>
. There is no reason why the !
operator on Nullable<bool>
should be defined in terms of bool
.
Quoting Microsoft:
When performing comparisons with nullable types, if one of the nullable types is null, the comparison is always evaluated to be false.
The rule makes no mention of implicit conversion. It is just an arbitrary convention that is meant to guarantee that no Boolean expressions have exceptions. Once the rule is in place we know how to write code. Sadly Microsoft missed this unary operator. To be consistent with the binary operators behavior, the following code should have a happy ending.
Therefore
static void Main(string[] args)
{
bool? fred = null;
if (!fred)
{
Console.WriteLine("you should not see this");
}
else
{
Console.WriteLine("Microsoft fixed this in 4.5!!!");
}
}
I bet you there are programmers that are now having to write fred==false
while Microsoft fixes this seemingly last null issue.
Upvotes: 0
Reputation:
Technically the bare conditional test doesn't require an implicit conversion to bool if you have an implementation of the true operator.
bool? nullableBool = null;
SqlBoolean sqlBoolean = SqlBoolean.Null;
bool plainBool = sqlBoolean; // won't compile, no implicit conversion
if (sqlBoolean) { } // will compile, SqlBoolean implements true operator
The original question is looking for a SQL style implementation of nulls where null is treated more like an unknown, while the Nullable implementation is more like adding null as an extra possible value. For example compare:
if (((int?)null) != 0) { } //block will execute since null is "different" from 0
if (SqlInt32.Null != 0) { } // block won't execute since "unknown" might have value 0
The more database like behavior is available from the types in System.Data.SqlTypes
Upvotes: 0
Reputation: 1500535
There's no implicit conversion from Nullable<bool>
to bool
. There is an implicit conversion from bool
to Nullable<bool>
and that's what happens (in language terms) to each of the bool constants in the first version. The bool operator==(Nullable<bool>, Nullable<bool>
operator is then applied. (This isn't quite the same as other lifted operators - the result is just bool
, not Nullable<bool>
.)
In other words, the expression 'fred == false' is of type bool
, whereas the expression 'fred' is of type Nullable<bool>
hence you can't use it as the "if" expression.
EDIT: To answer the comments, there's never an implicit conversion from Nullable<T>
to T
and for good reason - implicit conversions shouldn't throw exceptions, and unless you want null
to be implicitly converted to default(T)
there's not a lot else that could be done.
Also, if there were implicit conversions both ways round, an expression like "nullable + nonNullable" would be very confusing (for types that support +, like int
). Both +(T?, T?) and +(T, T) would be available, depending on which operand were converted - but the results could be very different!
I'm 100% behind the decision to only have an explicit conversion from Nullable<T>
to T
.
Upvotes: 61
Reputation: 4963
If you cast fred to boolean, it will compile :
if (( bool )fred )
(...)
I think that when you compare bool? to bool, the compiler make an implicite cast, do the comparison, and then return true or false. Result : the expression evaluate to a bool.
When you don't compare bool? to something, the expression evaluate to a bool?, who's illegal there.
Upvotes: 0
Reputation: 116478
The statement Nullable<bool> == true
is implicitly checking Nullable<bool> == (Nullable<bool>)true
.
Note that Nullable<bool>
itself is not a boolean. It is a wrapper for a boolean that can also be set to null.
Upvotes: 3