Optymalny sposób użycia zerowych operatorów warunkowych w wyrażeniach boolowskich

11

Piszesz wyrażenie logiczne, które może wyglądać następująco:

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

... i pojawia się błąd:

Operatora „&&” nie można stosować do operandów typu „bool” i „bool?”

Jaki jest optymalny / najczystszy sposób, aby sobie z tym poradzić?

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

    Czy to naprawdę czytelne?

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

    Może nie działać w LINQ-to-Entities ...

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

    Czy naprawdę napisałbyś if (condition == true)bez wahania?

Czy są jakieś inne opcje? Czy ostatecznie lepiej jest napisać:

  1. team.Category == "A Team" && team.Manager != null && team.Manager.IsVietnamVet
Santhos
źródło
if (! (String.IsNullOrEmpty (team.Manager) && condition)) ...
Snoop
1
@StevieV od samego początku miało być tak;)
Santhos
1
Przy dokładniejszym przyjrzeniu się kodowi część warunkowa null powinna być team.Manager?.IsVietnamVet, tzn. Później nie ma warunku null team, ponieważ już nie może być null.
svick
2
„Czy naprawdę napisałbyś, gdyby (warunek == prawda) bez wahania?” Dla normalnej wartości logicznej, nie, ponieważ jest zbyteczna. Jest to jednak istotne w przypadku wartości logicznej zerowalnej . nullableBool == truejest w zasadzie testowanie pod kątem nullableBool != false && nullableBool != null(i ta druga część sprawia, że ​​jest przydatna, a zatem nie jest zbyteczna)
Flater
2
Zacząłem używać, nullableBool == truejeśli nie utworzę opakowania. Jest czytelny, ponieważ normalnie nie piszesz, == truegdy używasz zwykłej wartości logicznej, jak wspomina @Flater, więc sugeruje, że zmienna ma wartość null. Dodatkowo poprawia to czytelność LINQ, ponieważ nie używasz wielu warunków zerowych, jak wspomina @Fabio.
Santhos

Odpowiedzi:

4

W tym konkretnym przypadku rozsądne może być przestrzeganie Prawa Demetera, tj

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

Mówiąc bardziej ogólnie, jeśli wyrażenie boolowskie jest złożone lub brzydkie, nie ma nic do powiedzenia, że ​​nie można go podzielić (lub jego części) na osobne stwierdzenie:

bool isVietnamVet = Manager?.IsVietnamVet ?? false;

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

Podczas przechodzenia przez kod w debuggerze często fajniej jest mieć rozbudowane warunki spakowane w jeden, boolaby zaoszczędzić trochę najechania kursorem myszy; w rzeczywistości lepiej byłoby umieścić całość w boolzmiennej.

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

if (isVietnamVetAndCategoryA)

lub z 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
źródło
Myślę, że głównie skłaniam się ku takiemu rozwiązaniu.
Santhos
1
Nowa składnia C #, aby zapisać kilka wierszy: public bool IsManagerVietnamVet => (team.Category == "") && (team.Manager? .IsVietnamVet ?? false);
Graham
Pamiętaj tylko, że długie a && b && c &&...są wykonywane sekwencyjnie, a następny nie jest nawet wykonywany, jeśli poprzedni jest false. Dlatego ludzie zwykle stawiają na początku najszybciej. Miej to na uwadze, przenosząc rzeczy do osobnego bool-var
jitbit
@jitbit Chodzi bardziej o łatwość konserwacji. Mikrooptymalizacje, takie jak ta, o której wspominasz, są na ogół niepotrzebne - nie warto się martwić, chyba że wydajność zostanie zidentyfikowana jako problem, a profilowanie pokaże, że wprowadzenie takich zmian miałoby zauważalny wpływ.
Ben Cottrell,
@BenCottrell Nazywałbym to optymalizacją „mikro”, jeśli IsManagerVietnamVetwłaściwość przejdzie do sprawdzenia w bazie danych;)
jitbit
3

Myślę, że opcja 3 (tj. == true) Jest najczystszym sposobem sprawdzenia, czy bool?jest true, ponieważ jest bardzo jasne, co robi.

W większości kod x == truenie ma sensu, ponieważ jest taki sam jak x, ale nie dotyczy to tutaj, więc myślę, == trueże nie byłoby to bardzo mylące.

svick
źródło
1
Myślę, że zdecydowanie byłby to właściwy sposób, gdybyśmy chcieli użyć operatora warunkowego o wartości zerowej, ponieważ jest on również bezpieczny dla linq. Pytanie brzmi, czy czytelność jest dobra. Z mojego punktu widzenia wybór jest między opcją 3 a opcją 4, a ze względu na czytelność wybrałbym 4 na 3, również intencja programisty wydaje się nieco jaśniejsza. Oznacziłem odpowiedź Bena Cottrella jako poprawną, ponieważ podoba mi się to podejście. Gdybym mógł zaznaczyć dwie odpowiedzi, zrobiłbym to.
Santhos
1
@ Santhos - re „intencja programisty wydaje się nieco jaśniejsza”. IMHO, jest to przypadek, w którym programista po raz pierwszy widzi, a?.b == trueże będą zdezorientowani, ale gdy zrozumieją, co robi, staje się bardzo czytelnym idiomem, znacznie ładniejszym niż konieczność testowania != nullw środku złożonego wyrażenia. To samo dotyczy a?.b ?? false, które wydaje się zyskać na popularności jako najbardziej popularne rozwiązanie, ponieważ pasuje do tego, co wpisałeś, gdybyś miał do czynienia z typem innym niż boolean [choć nie podoba mi się to dla boolean; dla mnie nie czyta się tak naturalnie; Wolę == true].
ToolmakerSteve
3

Rozwijając odpowiedź Bena Cottrella, wzór „Null Object” może ci pomóc.

Zamiast zwracać nullzespół / menedżera, wyodrębnij ITeami IManagerinterfejsy oraz zwróć sensowne alternatywne implementacje:

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

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

    public IManager Manager => new NoManager();
}

Nagle możesz to zrobić team.ManagedByVietnamVetbezpiecznie.

To oczywiście zależy od dostawcy, teamktóry ma być zerowo bezpieczny - ale można to zapewnić za pomocą odpowiednich testów.

Ant P.
źródło
-4

Napisałem prostą klasę, której możesz użyć:

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

Oczywiście niezawodne jest użycie niestandardowego typu. Możesz porównać MyBoolz obu booli Nullable<bool>.

Logerfo
źródło
1
Ta strona zawiera pytania dotyczące projektowania oprogramowania, a nie sposobu uzyskania określonych fragmentów kodu, więc jeśli uważasz, że jest to najlepsze rozwiązanie, twoja odpowiedź powinna skupić się na tym, dlaczego uważasz, że taka klasa jest najlepszym możliwym rozwiązaniem, a nie potrzebny jest dokładny kod, aby go zaimplementować.
Ixrec