Почему nullable bools не допускает, если (nullable), но разрешить if (nullable == true)?
Этот код компилируется:
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) должно было быть избыточным. Почему это не так?
Ответы
Ответ 1
Нет никакого неявного преобразования от Nullable<bool>
до bool
. Существует неявное преобразование от bool
до Nullable<bool>
и что происходит (в терминах языка) с каждой из констант bool в первой версии. Затем применяется оператор bool operator==(Nullable<bool>, Nullable<bool>
. (Это не совсем то же самое, что и другие поднятые операторы - результат равен bool
, а не Nullable<bool>
.)
Другими словами, выражение 'fred == false' имеет тип bool
, тогда как выражение 'fred' имеет тип Nullable<bool>
, поэтому вы не можете использовать его в качестве выражения if.
EDIT: для ответа на комментарии никогда не происходит неявного преобразования от Nullable<T>
до T
, и по уважительной причине - неявные преобразования не должны генерировать исключения, и если вы не хотите, чтобы null
был неявно преобразован в default(T)
не так много, что можно было бы сделать.
Кроме того, если бы неявные преобразования оба пути были округлены, выражение типа "nullable + nonNullable" было бы очень запутанным (для типов, которые поддерживают +, например, int
). Будут доступны как + (T?, T?), Так и + (T, T), в зависимости от того, какой операнд был преобразован, но результаты могут быть самыми разными!
Я на 100% отстаю от решения только иметь явное преобразование от Nullable<T>
до T
.
Ответ 2
Потому что fred не является логическим. это структура, которая имеет логическое свойство 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
Оператор Nullable<bool> == true
неявно проверяет Nullable<bool> == (Nullable<bool>)true
.
Обратите внимание, что Nullable<bool>
сам по себе не является логическим. Это оболочка для булева, которая также может быть установлена в значение null.
Ответ 4
Проблема реализации полностью заявлена, говоря: Fred
имеет тип Nullable<bool>
, а оператор !
не определен для Nullable<bool>
. Нет причин, по которым оператор !
на Nullable<bool>
должен быть определен в терминах bool
.
Цитата Microsoft:
При выполнении сравнений с типами NULL, если один из nullable types имеет значение null, сравнение всегда оценивается как false.
В правиле не упоминается неявное преобразование. Это просто произвольное соглашение, которое призвано гарантировать, что никакие булевы выражения не имеют исключений. Как только правило будет на месте, мы знаем, как писать код. К сожалению, 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 исправляет эту, казалось бы, последнюю ошибку.
Ответ 5
Если вы добавите fred в boolean, он скомпилирует:
if (( bool )fred )
(...)
Я думаю, что когда вы сравниваете bool? для bool, компилятор делает имплицитное литье, выполняет сравнение, а затем возвращает true или false. Результат: выражение оценивается как bool.
Когда вы не сравниваете bool? что-то, выражение оценивается в bool?, кто незаконно существует.
Ответ 6
Технически голый условный тест не требует неявного преобразования в 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
Исходный вопрос - это поиск реализаций NULL в стиле SQL, где null обрабатывается больше как неизвестный, в то время как реализация Nullable больше напоминает добавление null в качестве дополнительного возможного значения. Например, сравните:
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