Maneira ideal de usar operadores condicionais nulos em expressões booleanas

11

Você está escrevendo uma expressão booleana que pode ser assim:

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

... e você recebe um erro:

O operador '&&' não pode ser aplicado a operandos do tipo 'bool' e 'bool?'

Qual é a maneira ideal / mais limpa de lidar com isso?

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

    Isso é realmente legível?

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

    Pode não funcionar no LINQ-to-Entities ...

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

    Você realmente escreveria if (condition == true)sem hesitar?

Existem outras opções? Em última análise, é melhor escrever:

  1. team.Category == "A Team" && team.Manager != null && team.Manager.IsVietnamVet
Santhos
fonte
if ((String.IsNullOrEmpty (team.Manager) && condição)!) ...
Snoop
1
@StevieV, era mentir para ser assim desde o início;)
Santhos
1
Observando o código com mais cuidado, a parte condicional nula deve ser team.Manager?.IsVietnamVet, ou seja, nenhuma condição nula depois team, pois já não pode ser null.
svick
2
"Você realmente escreveria se (condição == verdadeiro) sem qualquer hesitação?" Para um booleano normal, não, pois é supérfluo. No entanto, para um booleano anulável , isso é relevante. nullableBool == trueé basicamente testando para nullableBool != false && nullableBool != null(e é esta segunda parte que faz com que seja útil e, portanto, não supérfluo)
Flater
2
Comecei a usar nullableBool == truese não criar um invólucro. É legível porque você normalmente não escreve == trueao usar booleano regular como o @Flater menciona, portanto, sugere que a variável é anulável. Além disso, melhora a legibilidade do LINQ porque você não usa várias condições nulas, como o @Fabio menciona.
Santhos

Respostas:

4

Nesse caso em particular, pode ser prudente seguir a Lei de Deméter, ou seja,

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

De maneira mais geral, se uma expressão booleana é complexa ou feia, não há nada a dizer que você não possa dividi-la (ou parte dela) em uma declaração separada:

bool isVietnamVet = Manager?.IsVietnamVet ?? false;

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

Ao percorrer o código no depurador, geralmente é melhor ter condições elaboradas agrupadas em um único boolapenas para economizar um pouco de pairar o mouse; de fato, pode ser melhor colocar a coisa toda em uma boolvariável.

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

if (isVietnamVetAndCategoryA)

ou com 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
fonte
Acho que estou mais inclinado a encontrar uma solução desse tipo.
Santhos 29/03/16
1
Nova sintaxe do C # para salvar algumas linhas: public bool IsManagerVietnamVet => (team.Category == "") && (team.Manager? .IsVietnamVet ?? false);
Graham
Apenas lembre-se de que long a && b && c &&...é executado sequencialmente, e o próximo nem é executado se o anterior false. Então, as pessoas costumam colocar mais rápido no começo. Tenha isso em mente quando se deslocam coisas em separado bool-var
Jitbit
@jitbit É mais sobre manutenção. As micro-otimizações, como a que você mencionou, geralmente são desnecessárias - não vale a pena se preocupar, a menos que o desempenho seja identificado como um problema e o perfil mostre que fazer alterações como essa teria algum impacto perceptível.
Ben Cottrell
@BenCottrell eu chamaria isso de um "micro" otimização se o IsManagerVietnamVetimóvel vai verificar numa base de dados;)
Jitbit
3

Eu acho que a opção 3 (ie == true) é a maneira mais limpa de teste que um bool?é true, porque é muito claro sobre o que ele faz.

Na maioria dos códigos x == true, não faz sentido, porque é o mesmo que x, mas isso não se aplica aqui, então acho == trueque não seria muito confuso.

svick
fonte
1
Eu acho que esse seria definitivamente o caminho a seguir, se quiséssemos usar o operador condicional nulo, porque também é seguro para linq. A questão é se a legibilidade é boa. Do meu ponto de vista, a escolha é entre opt 3 e opt 4 e eu escolheria 4 sobre 3 pelo motivo da legibilidade, também a intenção do programador parece um pouco mais clara. Marquei a resposta de Ben Cottrell como correta, porque gosto dessa abordagem um pouco mais. Se eu pudesse marcar duas respostas, eu o faria.
Santhos 29/03/16
1
@Santhos - re "a intenção do programador parece um pouco mais clara". IMHO, este é um caso em que a primeira vez que um programador vê a?.b == trueque ficará confuso, mas depois de entender o que está fazendo, torna-se um idioma muito legível, muito mais agradável do que ter que testar != nullno meio de uma expressão complexa. O mesmo para a?.b ?? false, que parece ter ganhado força como a solução mais popular, porque corresponde ao que você digitaria se estivesse lidando com um tipo diferente de booleano [embora eu não goste de booleano; para mim, não parece tão natural; Eu prefiro == true]
Home
3

Expandindo a resposta de Ben Cottrell, o padrão "Objeto nulo" pode ajudá-lo ainda mais.

Em vez de retornar uma nullequipe / gerente, extraia ITeame faça IManagerinterfaces e retorne implementações alternativas significativas:

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

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

    public IManager Manager => new NoManager();
}

De repente, você pode fazer team.ManagedByVietnamVetcom segurança.

Obviamente, isso depende do provedor upstream teamcomo sendo seguro para nulos - mas isso pode ser garantido com testes apropriados.

Formiga P
fonte
-4

Eu escrevi uma classe simples que você poderia 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();
        }
    }

Se for confiável usar um tipo personalizado, é claro. Você pode comparar MyBoolcom ambos boole Nullable<bool>.

Logerfo
fonte
1
Este site é para perguntas sobre design de software, e não sobre como obter trechos de código específicos para trabalhar. Portanto, se você acredita que esta é a melhor solução, sua resposta deve se concentrar no motivo pelo qual você acredita que uma classe como essa é a melhor solução possível, não a código exato necessário para implementá-lo.
Ixrec 27/03/19