Typy dopuszczające wartość null: lepszy sposób sprawdzania wartości null lub zera w języku C #

85

Pracuję nad projektem, w którym w wielu, wielu miejscach sprawdzam następujące kwestie:

if(item.Rate == 0 || item.Rate == null) { }

bardziej jako ciekawostka niż cokolwiek innego, jak najlepiej sprawdzić oba przypadki?

Dodałem metodę pomocniczą, która jest:

public static bool nz(object obj)
{
    var parsedInt = 0;
    var parsed = int.TryParse(obj.ToString(), out parsedInt);
    return IsNull(obj) || (parsed && parsedInt == 0);
}

Czy jest lepszy sposób?

Nailitdown
źródło

Odpowiedzi:

161

podoba mi się if ((item.Rate ?? 0) == 0) { }

Aktualizacja 1:

Możesz także zdefiniować metodę rozszerzenia, taką jak:

public static bool IsNullOrValue(this double? value, double valueToCheck)
{
    return (value??valueToCheck) == valueToCheck;
}

I użyj tego w ten sposób:

if(item.IsNullOrValue(0)){} // ale niewiele z tego czerpiesz

eglasius
źródło
3
dziękuję - bardzo zwięźle! martwiłem się o czytelność, ale doszedłem do wniosku, że byłoby to doskonale czytelne, gdybym rzeczywiście zrozumiał ?? operator.
Nailitdown
2
Powinieneś użyć metody rozszerzenia; chociaż jest czytelny w momencie pisania, te małe bryłki kodu wymagają ułamka przemyślenia, co oznacza, że ​​jeśli spróbujesz odczytać kod i go użyje - jesteś odciągnięty od głównego problemu.
konfigurator
11
@nailitdown: niezupełnie, potrzebujesz tylko metody rozszerzenia dla Nullable <T>. podwójnie? jest aliasem dla Nullable <double>. Twój podpis będzie wyglądał następująco: public static bool IsNullOrValue <T> (this Nullable <T>, t valueToCheck) gdzie T: struct
Joshua Shannon
3
@nailitdown: (część II) Twoja instrukcja zwrotu wyglądałaby wtedy jak return (value ?? default (T)). Equals (valueToCheck);
Joshua Shannon
2
Jeśli skróciłbyś go do „IsNullOr (0)”, naturalnie brzmiałoby to jako „jest null lub zero”
ChrisFox,
41

Korzystanie z typów ogólnych:

static bool IsNullOrDefault<T>(T value)
{
    return object.Equals(value, default(T));
}

//...
double d = 0;
IsNullOrDefault(d); // true
MyClass c = null;
IsNullOrDefault(c); // true

Jeśli Tjest to typ referencyjny , valuezostanie porównany z null( default(T)), w przeciwnym razie, jeśli Tto value type, powiedzmy double, default(t)to 0d, dla bool is false, for char is '\0'i tak dalej ...

Christian C. Salvadó
źródło
1
metoda rozszerzenia:public static bool IsNullOrValue<T>(this T? value, T valueToCheck) where T : struct { return (value ?? default(T)).Equals(valueToCheck); }
Behzad Ebrahimi
39

Chociaż podoba mi się przyjęta odpowiedź, myślę, że dla kompletności tę opcję należy również wspomnieć:

if (item.Rate.GetValueOrDefault() == 0) { }

To rozwiązanie


¹ Nie powinno to jednak wpłynąć na Twoją decyzję, ponieważ jest mało prawdopodobne, aby tego rodzaju mikro-optymalizacja miała jakiekolwiek znaczenie.

Heinzi
źródło
19

To naprawdę tylko rozwinięcie zaakceptowanej odpowiedzi Freddy'ego Riosa tylko przy użyciu Generics.

public static bool IsNullOrDefault<T>(this Nullable<T> value) where T : struct
{
    return default(T).Equals( value.GetValueOrDefault() );
}

public static bool IsValue<T>(this Nullable<T> value, T valueToCheck) where T : struct
{
    return valueToCheck.Equals((value ?? valueToCheck));
}

UWAGA nie musimy sprawdzać wartości default (T) dla null, ponieważ mamy do czynienia z typami wartości lub strukturami! Oznacza to również, że możemy bezpiecznie założyć, że wartość T valueToCheck nie będzie zerowa; Pamiętasz tutaj, że T? jest skrótem Nullable <T>, więc dodając rozszerzenie do Nullable <T> otrzymujemy metodę w int ?, double ?, bool? itp.

Przykłady:

double? x = null;
x.IsNullOrDefault(); //true

int? y = 3;
y.IsNullOrDefault(); //false

bool? z = false;
z.IsNullOrDefault(); //true
Joshua Shannon
źródło
2

Zgadzam się z użyciem ?? operator.

Jeśli masz do czynienia z ciągami znaków, użyj if (String.IsNullOrEmpty (myStr))

Nick Josevski
źródło
2

czy jest lepszy sposób?

Cóż, jeśli naprawdę szukasz lepszego sposobu, prawdopodobnie możesz dodać kolejną warstwę abstrakcji do Rate. Oto coś, co właśnie wymyśliłem, używając wzorca projektowego Nullable.

using System;
using System.Collections.Generic;

przestrzeń nazw NullObjectPatternTest
{
    Program zajęć publicznych
    {
        public static void Main (string [] args)
        {
            var items = nowa lista
                            {
                                nowy element (RateFactory.Create (20)),
                                nowy przedmiot (RateFactory.Create (null))
                            };

            PrintPricesForItems (elementy);
        }

        private static void PrintPricesForItems (IEnumerable items)
        {
            foreach (zmienna pozycja w pozycjach)
                Console.WriteLine ("Cena pozycji: {0: C}", item.GetPrice ());
        }
    }

    publiczna klasa abstrakcyjna ItemBase
    {
        public abstract Rate Rate {get; }
        public int GetPrice ()
        {
            // NIE ma potrzeby sprawdzania, czy Rate == 0 lub Rate == null
            zwrot 1 * Rate.Value;
        }
    }

    klasa publiczna Item: ItemBase
    {
        prywatny tylko do odczytu Rate _Rate;
        public override Rate Rate {get {return _Rate; }}
        pozycja publiczna (stawka stawki) {_Rate = stopa; }
    }

    publicznie zapieczętowana klasa RateFactory
    {
        publiczne tworzenie stawki statycznej (int? rateValue)
        {
            if (! rateValue || rateValue == 0) 
                return new NullRate ();
            return new Rate (rateValue);
        }
    }

    klasa publiczna Kurs
    {
        public int Value {get; zestaw; }
        publiczne wirtualne bool HasValue {get {return (Value> 0); }}
        public Stawka (wartość int) {Wartość = wartość; }
    }

    klasa publiczna NullRate: Rate
    {
        public override bool HasValue {get {return false; }}
        public NullRate (): base (0) {}
    }
}
dance2die
źródło
1
Myślę, że masz rację, że wyeliminowanie wartości zerowych na jakimś wcześniejszym etapie byłoby najlepszym rozwiązaniem
nailitdown
Nie dokładnie. Istnieje koncepcja zwana „refaktoryzacją”. Możesz zmienić kod w kierunku lepszego wzorca lub lepszej struktury. Na późniejszych etapach zawsze można wyeliminować wartości dopuszczające wartość null.
dance2die
2

Twój przykładowy kod nie powiedzie się. Jeśli obj ma wartość null, wówczas obj.ToString () spowoduje wyjątek odwołania o wartości null. Skróciłbym proces i sprawdziłbym, czy na początku twojej funkcji pomocniczej nie ma null obj. A jeśli chodzi o twoje aktualne pytanie, jakiego typu sprawdzasz pod kątem wartości null lub zero? W String jest świetna funkcja IsNullOrEmpty, wydaje mi się, że byłoby to świetne użycie metod rozszerzających do zaimplementowania metody IsNullOrZero w int? rodzaj.

Edycja: pamiętaj, „?” jest tylko cukrem kompilatora dla typu INullable, więc prawdopodobnie możesz wziąć INullable jako parametr, a następnie porównać go z wartością null (parm == null), a jeśli nie, to porównać do zera.

Walden Leverich
źródło
okrzyki za złapanie tego błędu - w tym projekcie muszę sprawdzić int ?, double?, decimal? itd., dlatego używam „object”
nailitdown
Zapamiętaj '?' jest po prostu cukrem kompilatora dla typu INullable <T>, więc prawdopodobnie możesz wziąć INullable <T> jako parametr, a następnie porównać go z wartością null (parm == null), a jeśli nie, to porównać do zera.
Walden Leverich,
0
public static bool nz(object obj)
{
    return obj == null || obj.Equals(Activator.CreateInstance(obj.GetType()));
}

źródło
1
Refleksja jest powolna , uważaj na takie rozwiązania. Nie sprzeciwiam się refleksji, ale nie spodziewałbym się tego w „prostej” funkcji takiej jak ta i zaskoczyłoby mnie to.
Walden Leverich
czy to faktycznie sprawdza zero?
nailitdown
Właściwie nie. Wierzę, że Activator.CreateInstance (obj.GetType ()) zwróci wartość null dla typów dopuszczających wartość null.
konfigurator
@konfigurator: gdy typy dopuszczające wartość null są opakowane, są one opakowane jako struktura bazowa, tj. nie spowoduje to skonstruowania wartości null dopuszczającej wartość null, ale strukturę o wartości domyślnej, która nie dopuszcza wartości null.
Eamon Nerbonne
0
class Item{  
 bool IsNullOrZero{ get{return ((this.Rate ?? 0) == 0);}}
}
dtroy
źródło
Twoje rozwiązanie działa dla jednej nieruchomości, powinienem był określić, że mam wiele właściwości tej samej pozycji, aby sprawdzić wartość null / zero
nailitdown
0

Nie zapominaj, że w przypadku stringów zawsze możesz użyć:

String.IsNullOrEmpty(str)

Zamiast:

str==null || str==""
Chris
źródło
1
Pytanie porównuje się z „0”, które nie jest pustym ciągiem.
dance2die
-1

O krok dalej od miłej odpowiedzi Joshuy Shannona . Teraz z zapobieganiem boksowaniu / rozpakowywaniu :

public static class NullableEx
{
    public static bool IsNullOrDefault<T>(this T? value)
        where T : struct
    {
        return EqualityComparer<T>.Default.Equals(value.GetValueOrDefault(), default(T));
    }
}
Deilan
źródło