Почему nullable bools не разрешают if (nullable), но разрешают if (nullable == true)? - PullRequest
33 голосов
/ 15 января 2009

Этот код компилируется:

    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");
        }
    }

Этот код не компилируется.

    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");
        }
    }

Я подумал, что (booleanExpression == true) должен был быть избыточным. Почему не в этом случае?

Ответы [ 6 ]

58 голосов
/ 15 января 2009

Нет неявного преобразования из Nullable<bool> в bool. - это неявное преобразование из bool в Nullable<bool>, и именно это происходит (в языковых терминах) с каждой из констант bool в первой версии. Затем применяется оператор bool operator==(Nullable<bool>, Nullable<bool>. (Это не совсем то же самое, что другие операторы отмены - результат просто bool, а не Nullable<bool>.)

Другими словами, выражение 'fred == false' имеет тип bool, тогда как выражение 'fred' имеет тип Nullable<bool>, следовательно, вы не можете использовать его как выражение "if".

РЕДАКТИРОВАТЬ: Чтобы ответить на комментарии, никогда не существует неявного преобразования из Nullable<T> в T и по уважительной причине - неявные преобразования не должны вызывать исключения, и если вы не хотите, чтобы null неявно преобразовывалось в default(T) не так много всего можно сделать.

Кроме того, если бы были неявными преобразованиями в обе стороны, выражение типа "nullable + nonNullable" было бы очень запутанным (для типов, которые поддерживают +, например, int). Оба + (T ?, T?) И + (T, T) будут доступны, в зависимости от того, какой операнд был преобразован - но результаты могут быть очень разными!

Я на 100% за решение иметь только явное преобразование из Nullable<T> в T.

8 голосов
/ 15 января 2009

Потому что Фред не логический. это структура, которая имеет логическое свойство IsNull, HasValue или что-то еще ... Объект с именем fred представляет собой сложный составной объект, содержащий логическое значение и значение, а не сам примитивный логический объект ...

Ниже, например, показано, как можно реализовать Nullable Int. Универсальный Nullable почти наверняка реализован аналогично (но в общем). Здесь вы можете увидеть, как реализованы неявные и явные преобразования.

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"; }   
   }
3 голосов
/ 15 января 2009

Оператор Nullable<bool> == true неявно проверяет Nullable<bool> == (Nullable<bool>)true.

Обратите внимание, что Nullable<bool> само по себе не является логическим значением. Это обёртка для логического значения, которое также может иметь значение null.

1 голос
/ 10 сентября 2011

Проблема реализации прекрасно сформулирована следующим образом: Fred имеет тип Nullable<bool>, а оператор ! не определен для Nullable<bool>. Нет причины, по которой оператор ! в Nullable<bool> должен быть определен в терминах bool.

Цитата Microsoft:

При выполнении сравнения с обнуляемыми типами, если один из Обнуляемые типы имеют значение null, сравнение всегда оценивается как ложное.

В правиле не упоминается неявное преобразование. Это просто произвольное соглашение, которое должно гарантировать, что никакие логические выражения не имеют исключений. Как только правило введено в действие, мы знаем, как писать код. К сожалению, Microsoft пропустила этот унарный оператор. Чтобы соответствовать поведению бинарных операторов, следующий код должен иметь счастливый конец.

Поэтому

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!!!");
    }
}

Могу поспорить, что есть программисты, которым теперь приходится писать fred==false, пока Microsoft исправляет эту, казалось бы, последнюю нулевую проблему.

0 голосов
/ 30 июля 2009

Технически простой условный тест не требует неявного преобразования в bool, если у вас есть реализация истинного оператора.

bool? nullableBool = null;
SqlBoolean sqlBoolean = SqlBoolean.Null;
bool plainBool = sqlBoolean; // won't compile, no implicit conversion
if (sqlBoolean) { } // will compile, SqlBoolean implements true operator

Первоначальный вопрос заключается в поиске нулевой реализации в стиле SQL, в которой ноль рассматривается скорее как неизвестное, а реализация Nullable больше похожа на добавление нулевого значения в качестве дополнительного возможного значения. Например сравнить:

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

Более похожее поведение базы данных доступно из типов в System.Data.SqlTypes

0 голосов
/ 15 января 2009

Если вы приведете fred к логическому значению, он скомпилирует:

  if (( bool )fred )
      (...)

Я думаю, что когда вы сравниваете bool? чтобы bool, компилятор делает неявное приведение, делает сравнение, а затем возвращает true или false. Результат: выражение оценивается как bool.

Когда вы не сравниваете bool? что-то, выражение оценивать как bool ?, кто там незаконен.

...