tags:

views:

1069

answers:

5

This code compiles:

    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.

    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?

+4  A: 

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 objet 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
Quibblesome
It's only supposed to be redundant when the LHS is a bool. In this case it's not.
Jon Skeet
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
... (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
+29  A: 

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 very good explaination
Andrew Hare
Any insight in to why the implicit conversion from Nullable<bool> to bool doesn't exist?
Scott Dorman
Thank you! :D. I'd also like to know why they haven't given us an implicit conversion here. Surely this is "fixable".....
Quibblesome
@Scott Dorman: If a Nullable<bool> has HasValue equal to false, what bool value would you implicitly convert it to?
Jason
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
100% agree on disallow implicit conversion from Nullable<T> -> T. Implicit conversions should never throw. It maintains the simple rules that implict = safe, explicit = dangerous.
JaredPar
@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
+2  A: 

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
A: 

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
A: 

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

Mike Harkavy