¿Por qué los bools anulables no permiten if (nullable) pero sí lo permiten if (nullable == true)?

StackOverflow https://stackoverflow.com/questions/447408

  •  22-07-2019
  •  | 
  •  

Pregunta

Este código compila:

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

Este código hace no compilar.

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

Pensé que se suponía que if(booleanExpression == true) era una redundancia.¿Por qué no es así en este caso?

¿Fue útil?

Solución

No hay ninguna conversión implícita de Nullable<bool> a bool.Allá es una conversión implícita de bool a Nullable<bool> y eso es lo que sucede (en términos de lenguaje) con cada una de las constantes bool en la primera versión.El bool operator==(Nullable<bool>, Nullable<bool> Luego se aplica el operador.(Esto no es exactamente lo mismo que otros operadores elevados; el resultado es simplemente bool, no Nullable<bool>.)

En otras palabras, la expresión 'fred == false' es de tipo bool, mientras que la expresión 'fred' es de tipo Nullable<bool> por lo tanto, no puede utilizarlo como expresión "si".

EDITAR:Para responder a los comentarios, nunca hay una conversión implícita de Nullable<T> a T y por una buena razón: las conversiones implícitas no deberían generar excepciones y, a menos que quieras null ser convertido implícitamente a default(T) no hay mucho más que se pueda hacer.

Además, si hay eran conversiones implícitas en ambos sentidos, una expresión como "nullable + nonNullable" sería muy confusa (para tipos que admiten +, como int).Tanto +(T?, T?) como +(T, T) estarían disponibles, dependiendo del operando que se haya convertido, ¡pero los resultados podrían ser muy diferentes!

Estoy 100% detrás de la decisión de tener solo una conversión explícita de Nullable<T> a T.

Otros consejos

Porque fred no es un booleano. es una estructura, que tiene una propiedad booleana llamada IsNull, o HasValue, o lo que sea ... El objeto llamado fred es el objeto compuesto complejo que contiene un valor booleano y un valor, no un booleano primitivo en sí ...

A continuación, por ejemplo, es cómo se podría implementar un Nullable Int. El Nullable genérico casi seguramente se implementa de manera similar (pero genéricamente). Puede ver aquí cómo se implementan las conversiones implícitas y explícitas.

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

La declaración Nullable<bool> == true está comprobando implícitamente Nullable<bool> == (Nullable<bool>)true.

Tenga en cuenta que Nullable<bool> en sí no es un booleano. Es un contenedor para un booleano que también se puede establecer en nulo.

El problema de la implementación se expresa perfectamente al decir: Fred es de tipo Nullable<bool> y el ! El operador no está definido para Nullable<bool>.No hay ninguna razón por la cual el ! operador en Nullable<bool> debe definirse en términos de bool.

Citando a Microsoft:

Al realizar comparaciones con tipos anulables, si uno de los tipos anulables es nula, la comparación siempre se evalúa como falsa.

La regla no menciona la conversión implícita.Es simplemente una convención arbitraria que pretende garantizar que ninguna expresión booleana tenga excepciones.Una vez que la regla está implementada, sabemos cómo escribir código.Lamentablemente, Microsoft pasó por alto este operador unario.Para ser coherente con el comportamiento de los operadores binarios, el siguiente código debería tener un final feliz.

Por lo tanto

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

Apuesto a que hay programadores que ahora tienen que escribir fred==false mientras Microsoft soluciona este último problema aparentemente nulo.

Si lanzas fred a boolean, compilará:

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

Creo que cuando comparas bool? para bool, el compilador realiza un reparto improvisado, hace la comparación y luego devuelve verdadero o falso. Resultado: la expresión evalúa a bool.

Cuando no comparas bool? a algo, la expresión evalúa a un bool ?, quién es ilegal allí.

Técnicamente, la prueba condicional simple no requiere una conversión implícita a bool si tiene una implementación del operador verdadero.

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

La pregunta original está buscando una implementación de estilo nulo de SQL donde el nulo se trata más como un desconocido, mientras que la implementación Nullable es más como agregar nulo como un valor adicional posible. Por ejemplo, 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

El comportamiento más similar a la base de datos está disponible de los tipos en System.Data.SqlTypes

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top