Why do nullable bools not allow if(nullable) but do allow if(nullable == true)?
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
.
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"; }
}
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.