【问题标题】:Why do nullable bools not allow if(nullable) but do allow if(nullable == true)?为什么可为空的布尔值不允许 if(nullable) 但允许 if(nullable == true)?
【发布时间】:2009-01-15 16:17:33
【问题描述】:

这段代码编译:

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

这段代码编译。

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

我认为if(booleanExpression == true) 应该是多余的。为什么不是这种情况?

【问题讨论】:

    标签: c# .net nullable


    【解决方案1】:

    没有从Nullable<bool>bool 的隐式转换。 boolNullable<bool> 的隐式转换,这就是第一个版本中每个布尔常量发生的情况(在语言方面)。然后应用 bool operator==(Nullable<bool>, Nullable<bool> 运算符。 (这与其他提升运算符不太一样 - 结果只是bool,而不是Nullable<bool>。)

    换句话说,表达式“fred == false”的类型为bool,而表达式“fred”的类型为Nullable<bool>,因此您不能将其用作“if”表达式。

    编辑:要回答 cmets,永远不会有从 Nullable<T>T 的隐式转换,并且有充分的理由 - 隐式转换不应该引发异常,除非您希望将 null 隐式转换为 @987654333 @没有什么可以做的了。

    另外,如果个隐式转换,像“nullable + nonNullable”这样的表达式会很混乱(对于支持+的类型,比如int)。 +(T?, T?) 和 +(T, T) 都可用,具体取决于转换的操作数 - 但结果可能会有很大不同!

    我 100% 支持仅进行从 Nullable<T>T 的显式转换的决定。

    【讨论】:

    • 任何关于为什么从 Nullable 到 bool 的隐式转换不存在的见解?
    • 谢谢! :D。我也想知道为什么他们没有在这里给我们一个隐式转换。当然这是“可修复的”.....
    • @Scott Dorman:如果 Nullable 的 HasValue 等于 false,您会将其隐式转换为什么 bool 值?
    • 好吧,我猜你可能会创建某种异常,在这种情况下你可以以另一种方式翻转转换(bool 到可为空的 bool),或者至少将 if(fred) 转换为 if(fred == true) 在编译时。在目前的状态下,这对我来说似乎有点愚蠢。
    • 100% 同意禁止从 Nullable -> 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"; }   
       }
    

    【讨论】:

    • 是的,我很欣赏这一点,但 == true 应该 是多余的。这意味着使用通常多余的 == true 实际上比这样做更简洁:if(fred != null && fred.Value){}
    • 只有当 LHS 是布尔值时才应该是多余的。在这种情况下不是。
    • 或者,如果你真的希望能够编写“if (fred)”,那么编写你自己的自定义可空布尔值,public struct MyNullBool {} 仿照我上面的 DBInt,(续)
    • ... (con't) ... and 添加隐式转换,如 public static implicit operator bool(MyNullBool insideVal) {if ( !insideVal.defined) throw InvalidOperationException();返回 insideVal.value; } ----- 但我不推荐这个,因为它有点迟钝。
    【解决方案3】:

    语句Nullable&lt;bool&gt; == true 隐式检查Nullable&lt;bool&gt; == (Nullable&lt;bool&gt;)true

    请注意,Nullable&lt;bool&gt; 本身不是布尔值。它是一个布尔值的包装器,也可以设置为 null。

    【讨论】:

      【解决方案4】:

      如果你将 fred 转换为布尔值,它将编译:

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

      我认为当你比较 bool 时? bool,编译器进行隐式转换,进行比较,然后返回真或假。结果:表达式计算为布尔值。

      当你不比较布尔?对于某事,表达式的计算结果为 bool?,谁是非法的。

      【讨论】:

        【解决方案5】:

        从技术上讲,如果您实现了 true 运算符,那么裸条件测试不需要隐式转换为 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 风格的 null 实现,其中 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 中的类型提供了更多类似数据库的行为

        【讨论】:

          【解决方案6】:

          通过说:Fred 的类型为Nullable&lt;bool&gt; 并且! 运算符没有为Nullable&lt;bool&gt; 定义,可以完美地说明实现问题。没有理由将Nullable&lt;bool&gt; 上的! 运算符定义为bool

          引用微软:

          当执行与可为空类型的比较时,如果其中一个 可空类型为空,比较总是被评估为假。

          该规则没有提及隐式转换。这只是一个任意约定,旨在保证没有布尔表达式有异常。一旦规则到位,我们就知道如何编写代码。遗憾的是,微软错过了这个一元运算符。为了与二元运算符的行为保持一致,以下代码应该有一个美好的结局。

          因此

          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 修复了这个看似最后一个 null 的问题。

          【讨论】:

          • 这是错误的。 ! 为可为空的布尔值定义的。虽然首先定义它的原因很奇怪。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-01-09
          • 1970-01-01
          • 2015-10-04
          • 2013-05-08
          • 2012-08-12
          • 1970-01-01
          • 2019-01-21
          相关资源
          最近更新 更多