Forma óptima de usar operadores condicionales nulos en expresiones booleanas

11

Está escribiendo una expresión booleana que podría verse así:

team.Category == "A Team" && team?.Manager?.IsVietnamVet

public class Manager
{
    public bool IsVietnamVet { get; set; }
}

public class Team
{
    public string Category { get; set; }

    public Manager Manager { get; set; }
}

... y obtienes un error:

El operador '&&' no se puede aplicar a operandos de tipo 'bool' y 'bool?'

¿Cuál es la forma óptima / más limpia de manejarlo?

  1. team.Category == "A Team" && (team?.Manager?.IsVietnamVet ?? false)

    ¿Es eso realmente legible?

  2. team.Category == "A Team" && (team?.Manager?.IsVietnamVet).GetValueOrDefault()

    Es posible que no funcione en LINQ-to-Entities ...

  3. team.Category == "A Team" && team?.Manager?.IsVietnamVet == true

    ¿Realmente escribirías if (condition == true)sin dudarlo?

¿Hay más opciones? ¿Es finalmente mejor escribir:

  1. team.Category == "A Team" && team.Manager != null && team.Manager.IsVietnamVet
Santhos
fuente
if (! (String.IsNullOrEmpty (team.Manager) && condition)) ...
Snoop
1
@StevieV fue así desde el principio;)
Santhos
1
Mirando el código con más cuidado, la parte condicional nula debería ser team.Manager?.IsVietnamVet, es decir, no condicional nula después team, ya que ya no puede ser null.
svick
2
"¿Realmente escribirías si (condición == verdadero) sin ninguna duda?" Para un booleano normal, no, ya que es superfluo. Sin embargo, para un booleano anulable , esto es relevante. nullableBool == truebásicamente está probando nullableBool != false && nullableBool != null(y es esta segunda parte la que lo hace útil y, por lo tanto, no superfluo)
Flater
2
Empecé a usar nullableBool == truesi no creo un contenedor. Es legible porque normalmente no escribe == truecuando usa booleano regular como menciona @Flater, por lo que sugiere que la variable es anulable. Además, mejora la legibilidad de LINQ porque no utiliza múltiples condiciones nulas como menciona @Fabio.
Santhos

Respuestas:

4

En este caso particular, podría ser prudente seguir la Ley de Deméter, es decir

public class Team
{
    public bool IsManagerVietnamVet => Manager?.IsVietnamVet ?? false;
}    

En términos más generales, si una expresión booleana es compleja o fea, entonces no hay nada que decir que no podría dividirla (o parte de ella) en una declaración separada:

bool isVietnamVet = Manager?.IsVietnamVet ?? false;

if (team.Category == "A Team" && isVietnamVet)

Al recorrer el código en el depurador, a menudo es mejor tener condiciones elaboradas agrupadas en una sola boolpara ahorrar un poco de desplazamiento del mouse; de hecho, podría ser mejor poner todo en una boolvariable.

bool isVietnamVetAndCategoryA = (team.Category == "A Team"
    && Manager?.IsVietnamVet ?? false);

if (isVietnamVetAndCategoryA)

o con LINQ:

var wibble = from flight in airport
             from passenger in flight.manifest
             let isOnPlane = 
                 (flight.FinishedBoarding && passenger.Flight == flight.FlightNumber)
             where !isOnPlane
             select passenger;
Ben Cottrell
fuente
Creo que me estoy inclinando principalmente hacia tal solución.
Santhos
1
Nueva sintaxis de C # para guardar algunas líneas: public bool IsManagerVietnamVet => (team.Category == "") && (team.Manager? .IsVietnamVet ?? false);
Graham
Solo tenga en cuenta que los largos a && b && c &&...se ejecutan secuencialmente, y el siguiente ni siquiera se ejecuta si el anterior lo es false. Entonces la gente suele poner más rápido al principio. Tenga esto en cuenta cuando mueva cosas a un bool-var separado
jitbit
@jitbit Se trata más de mantenibilidad. Las micro optimizaciones como la que usted menciona son generalmente innecesarias: no vale la pena preocuparse a menos que el rendimiento se identifique como un problema y el perfil muestre que realizar cambios como ese tendría un impacto notable.
Ben Cottrell
@BenCottrell Lo llamaría una optimización "micro" si la IsManagerVietnamVetpropiedad va a registrarse en una base de datos;)
jitbit
3

Creo que la opción 3 (es decir == true) es la forma más limpia de probar que bool?es true, porque es muy claro acerca de lo que hace.

En la mayoría de los códigos x == trueno tiene sentido, porque es lo mismo x, pero eso no se aplica aquí, así que creo == trueque no sería muy confuso.

svick
fuente
1
Creo que este sería definitivamente el camino a seguir si quisiéramos usar el operador condicional nulo, porque también es seguro. La pregunta es si la legibilidad es buena. Desde mi punto de vista, la elección es entre opt 3 y opt 4 y elegiría 4 sobre 3 por la razón de la legibilidad, también la intención del programador parece un poco más clara. He marcado la respuesta de Ben Cottrell como correcta porque me gusta un poco más ese enfoque. Si pudiera marcar dos respuestas, lo haría.
Santhos
1
@Santhos - re "la intención del programador parece un poco más clara". En mi humilde opinión, este es un caso en el que la primera vez que un programador ve a?.b == trueque estará confundido, pero una vez que comprende lo que está haciendo, se convierte en un idioma muy legible, mucho más agradable que tener que probar != nullen medio de una expresión compleja. Lo mismo para a?.b ?? false, que parece haber ganado tracción como la solución más popular, porque coincide con lo que escribiría si estuviera tratando con un tipo que no sea ​​booleano [aunque no me gusta para booleano; para mí, no se lee tan naturalmente; Yo prefiero == true]
ToolmakerSteve
3

Ampliando la respuesta de Ben Cottrell, el patrón "Objeto nulo" puede ayudarlo aún más.

En lugar de devolver un nullequipo / gerente, extraiga ITeame IManagerinteractúe y devuelva implementaciones alternativas significativas:

public class NoManager : IManager
{
    public bool IsVietnamVet => false;
}

public class NoTeam : ITeam
{
    public bool ManagedByVietnamVet => false;

    public IManager Manager => new NoManager();
}

Entonces, de repente, puedes hacerlo con team.ManagedByVietnamVetseguridad.

Por supuesto, esto depende de que el proveedor ascendente teamsea ​​nulo-seguro, pero eso se puede garantizar con las pruebas adecuadas.

Hormiga P
fuente
-4

Escribí una clase simple que podrías usar:

 public class MyBool 
    {
        public bool? Value { get; set; }

        public MyBool(bool b)
        {
            Value = b;
        }

        public MyBool(bool? b)
        {
            Value = b;
        }

        public static implicit operator bool(MyBool m)
        {
            return m?.Value ?? false;
        }

        public static implicit operator bool?(MyBool m)
        {
            return m?.Value;
        }

        public static implicit operator MyBool(bool m)
        {
            return new MyBool(m);
        }

        public static implicit operator MyBool(bool? m)
        {
            return new MyBool(m);
        }

        public override string ToString()
        {
            return Value.ToString();
        }
    }

Si es confiable usar un tipo personalizado, por supuesto. Puedes comparar MyBoolcon ambos booly Nullable<bool>.

Logerfo
fuente
1
Este sitio es para preguntas sobre el diseño de software en lugar de cómo hacer que funcionen piezas específicas de código, por lo que si cree que esta es la mejor solución, su respuesta debería centrarse en por qué cree que una clase como esta es la mejor solución posible, no la código exacto necesario para implementarlo.
Ixrec