36

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?

AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
Quibblesome
  • 25,225
  • 10
  • 61
  • 100

6 Answers6

61

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Any insight in to why the implicit conversion from Nullable to bool doesn't exist? – Scott Dorman Jan 15 '09 at 16:24
  • Thank you! :D. I'd also like to know why they haven't given us an implicit conversion here. Surely this is "fixable"..... – Quibblesome Jan 15 '09 at 16:25
  • @Scott Dorman: If a Nullable has HasValue equal to false, what bool value would you implicitly convert it to? – jason Jan 15 '09 at 16:28
  • Well i'm guessing you could create some kind of exception where you flip the conversion the other way (bool to nullable bool) or at the very least convert if(fred) to if(fred == true) at compile time. This just seems a little silly to me in its present state. – Quibblesome Jan 15 '09 at 16:29
  • 2
    100% agree on disallow implicit conversion from Nullable -> T. Implicit conversions should never throw. It maintains the simple rules that implict = safe, explicit = dangerous. – JaredPar Jan 15 '09 at 16:47
  • @Jon Skeet, @Jason: Disregard my question...wasn't thinking about it at all or I would never have asked having already known the answer. :) Thanks. Anyway, I think the clarification makes this a more complete answer. I can't upvote again or I would. – Scott Dorman Jan 15 '09 at 17:32
8

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"; }   
   }
Charles Bretana
  • 143,358
  • 22
  • 150
  • 216
  • Yes, I appreciate this but == true is _supposed_ to be a redundancy. It means that using the usually redundant == true is actually more terse than doing: if(fred != null && fred.Value){} – Quibblesome Jan 15 '09 at 16:24
  • It's only supposed to be redundant when the LHS is a bool. In this case it's not. – Jon Skeet Jan 15 '09 at 16:30
  • or, if you really really want to be able to write " if (fred)", then write your own custom nullable boolean, public struct MyNullBool {} modeled after my DBInt above, (con't) – Charles Bretana Jan 15 '09 at 21:04
  • ... (con't) ... and and add an implicit conversion, as in public static implicit operator bool(MyNullBool insideVal) {if ( !insideVal.defined) throw InvalidOperationException(); return insideVal.value; } ----- But I would not recommend this as it is a bit obtuse. – Charles Bretana Jan 15 '09 at 21:05
3

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.

lc.
  • 113,939
  • 20
  • 158
  • 187
0

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

0

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.

Sylvain Rodrigue
  • 4,751
  • 5
  • 53
  • 67
0

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.

Arturo Hernandez
  • 2,749
  • 3
  • 28
  • 36
  • This is wrong. `!` *is* defined for nullable bools. Although why it is defined in the first place is bizarre. – MgSam Aug 31 '20 at 14:57