Jaki jest właściwy sposób sprawdzania wartości null?


122

Uwielbiam operator łączenia wartości null, ponieważ ułatwia przypisywanie wartości domyślnej dla typów dopuszczających wartość null.

 int y = x ?? -1;

To świetnie, z wyjątkiem sytuacji, gdy muszę zrobić coś prostego x. Na przykład, jeśli chcę to sprawdzić Session, zwykle muszę napisać coś bardziej szczegółowego.

Chciałbym móc to zrobić:

string y = Session["key"].ToString() ?? "none";

Ale nie możesz, ponieważ .ToString()zostaje wywołany przed sprawdzeniem zerowej wartości, więc kończy się niepowodzeniem, jeśli Session["key"]jest zerowy. W końcu robię to:

string y = Session["key"] == null ? "none" : Session["key"].ToString();

Działa i moim zdaniem jest lepsza niż trzywierszowa alternatywa:

string y = "none";
if (Session["key"] != null)
    y = Session["key"].ToString();

Mimo że to działa, wciąż jestem ciekawy, czy istnieje lepszy sposób. Wydaje się, że nie ma znaczenia, do czego zawsze muszę się odwoływać Session["key"]dwukrotnie; raz do sprawdzenia i jeszcze raz do zadania. Jakieś pomysły?


20
To wtedy chciałbym, żeby C # miał „bezpiecznego operatora nawigacji” ( .?), jak ma Groovy .
— Cameron

2
@Cameron: To jest, gdy chciałbym, aby C # mógł traktować typy dopuszczające wartość null (w tym typy referencyjne) jako monadę, więc nie potrzebujesz „bezpiecznego operatora nawigacji”.
— Jon Purdy

3
Twórca zerowych odniesień nazwał to swoim „miliardowym błędem” i zwykle się z tym zgadzam. Zobacz infoq.com/presentations/…
— Jamie Ide

Jego faktycznym błędem jest niebezpieczne (nie wymuszane językiem) mieszanie typów dopuszczających wartość null i nie-nullabelowych.
— MSalters

@JamieIde Dzięki za bardzo ciekawy link. :)
— BobRodes

Odpowiedzi:


182

Co powiesz na

string y = (Session["key"] ?? "none").ToString();

79
Siła jest mocna z tym.
— Chev

2
@Matthew: Nie, ponieważ wartości Session są typu Object
— BlackBear

1
@BlackBear, ale zwrócona wartość jest najprawdopodobniej ciągiem, więc rzutowanie jest prawidłowe
— Firo

To była najbardziej bezpośrednia odpowiedź na moje pytanie, więc zaznaczam odpowiedź, ale metoda rozszerzania Jona Skeeta .ToStringOrDefault()jest moim preferowanym sposobem robienia tego. Jednak używam tej odpowiedzi w ramach metody rozszerzenia Jona;)
— Chev

10
Nie podoba mi się to, ponieważ jeśli masz w sesji inny typ obiektu niż ten, którego się spodziewasz, możesz ukrywać pewne subtelne błędy w swoim programie. Wolałbym raczej użyć bezpiecznego rzutu, ponieważ myślę, że prawdopodobnie szybciej wykryje błędy. Unika również wywoływania ToString () na obiekcie ciągu.
— tvanfosson

130

Jeśli często robisz to specjalnie z,ToString() możesz napisać metodę rozszerzającą:

public static string NullPreservingToString(this object input)
{
    return input == null ? null : input.ToString();
}

...

string y = Session["key"].NullPreservingToString() ?? "none";

Lub oczywiście domyślna metoda:

public static string ToStringOrDefault(this object input, string defaultValue)
{
    return input == null ? defaultValue : input.ToString();
}

...

string y = Session["key"].ToStringOrDefault("none");

16
.ToStringOrDefault()jest prosty i elegancki. Fajne rozwiązanie.
— Chev

7
W ogóle nie mogę się z tym zgodzić. Metody rozszerzające objectsą przekleństwem i śmieciami w bazie kodu, a metody rozszerzające, które działają bez błędów na thiswartościach null , są czystym złem.
— Nick Larsen

10
@NickLarsen: Wszystko z umiarem, mówię. Metody rozszerzeń, które działają z wartością null, mogą być bardzo przydatne, IMO - o ile mają jasność co do tego, co robią.
— Jon Skeet

3
@ one.beat.consumer: Tak. Gdyby to było tylko formatowanie (lub jakiekolwiek literówki), to byłaby jedna rzecz, ale zmiana wybranej przez autora nazwy metody wykracza poza normalnie odpowiednią edycję, IMO.
— Jon Skeet,

6
@ one.beat.consumer: Kiedy poprawiasz gramatykę i literówki, to w porządku - ale zmiana imienia, które ktoś (kogokolwiek, nie tylko ja) wybrał wyraźnie celowo, wydaje mi się inna. W tym miejscu sugerowałbym to w komentarzu.
— Jon Skeet

21

Możesz także użyć as, co daje, nulljeśli konwersja się nie powiedzie:

Session["key"] as string ?? "none"

To wróci "none"nawet jeśli ktoś nadziewane się intin Session["key"].


1
Działa to tylko wtedy, gdy nie potrzebujesz ToString()w pierwszej kolejności.
— Abel

1
Dziwię się, że nikt jeszcze nie odrzucił tej odpowiedzi. Jest to całkowicie odmienne semantycznie od tego, co chce zrobić PO.
— Timwi

@Timwi: OP używa ToString()do konwersji obiektu zawierającego ciąg znaków na ciąg. Możesz zrobić to samo za pomocą obj as stringlub (string)obj. To dość powszechna sytuacja w ASP.NET.
— Andomar

5
@Andomar: Nie, OP wywołuje ToString()obiekt (mianowicie Session["key"]), którego typu nie wymienił. Może to być dowolny obiekt, niekoniecznie ciąg.
— Timwi

13

Jeśli zawsze będzie string, możesz przesyłać:

string y = (string)Session["key"] ?? "none";

Ma to tę zaletę, że narzekają, zamiast ukrywać błąd, jeśli ktoś wbija intcoś lub coś w środku Session["key"]. ;)


10

Wszystkie sugerowane rozwiązania są dobre i odpowiedz na pytanie; więc to jest tylko nieznaczne wydłużenie. Obecnie większość odpowiedzi dotyczy tylko walidacji wartości null i typów ciągów. Możesz rozszerzyć StateBagobiekt, aby zawierał ogólny plikGetValueOrDefault metodę , podobną do odpowiedzi opublikowanej przez Jona Skeeta.

Prosta ogólna metoda rozszerzenia, która akceptuje ciąg jako klucz, a następnie typ sprawdza obiekt sesji. Jeśli obiekt ma wartość null lub nie jest tego samego typu, zwracana jest wartość domyślna, w przeciwnym razie zwracana jest wartość sesji o silnym typie.

Coś takiego

/// <summary>
/// Gets a value from the current session, if the type is correct and present
/// </summary>
/// <param name="key">The session key</param>
/// <param name="defaultValue">The default value</param>
/// <returns>Returns a strongly typed session object, or default value</returns>
public static T GetValueOrDefault<T>(this HttpSessionState source, string key, T defaultValue)
{
    // check if the session object exists, and is of the correct type
    object value = source[key]
    if (value == null || !(value is T))
    {
        return defaultValue;
    }

    // return the session object
    return (T)value;
}

1
Czy możesz dołączyć próbkę użycia dla tej metody rozszerzenia? Czy StateBag nie zajmuje się stanem widoku, a nie sesją? Używam ASP.NET MVC 3, więc nie mam prostego dostępu do widoku stanu. Myślę, że chcesz przedłużyć HttpSessionState.
— Chev

ta odpowiedź wymaga pobrania wartości 3x i 2 rzutów, jeśli się powiedzie. (wiem, że jest to słownik, ale początkujący mogą korzystać z podobnych praktyk na drogich metod.)
— Jake Berger

3
T value = source[key] as T; return value ?? defaultValue;
— Jake Berger,

1
@jberger Rzutowanie na wartość przy użyciu „as” jest niedostępne, ponieważ nie ma ograniczenia klasy dla typu ogólnego, ponieważ potencjalnie możesz chcieć zwrócić wartość, taką jak bool. @AlexFord Przepraszam, chciałbyś przedłużyć HttpSessionStatesesję. :)
— Richard

w rzeczy samej. jak zauważył Richard, wymaga ograniczenia. (... i inna metoda, jeśli chcesz użyć typów wartości)
— Jake Berger

7

Używamy metody o nazwie NullOr.

Stosowanie

// Call ToString() if it’s not null, otherwise return null
var str = myObj.NullOr(obj => obj.ToString());

// Supply default value for when it’s null
var str = myObj.NullOr(obj => obj.ToString()) ?? "none";

// Works with nullable return values, too —
// this is properly typed as “int?” (nullable int)
// even if “Count” is just int
var count = myCollection.NullOr(coll => coll.Count);

// Works with nullable input types, too
int? unsure = 47;
var sure = unsure.NullOr(i => i.ToString());

Źródło

/// <summary>Provides a function delegate that accepts only value types as return types.</summary>
/// <remarks>This type was introduced to make <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncStruct{TInput,TResult})"/>
/// work without clashing with <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncClass{TInput,TResult})"/>.</remarks>
public delegate TResult FuncStruct<in TInput, TResult>(TInput input) where TResult : struct;

/// <summary>Provides a function delegate that accepts only reference types as return types.</summary>
/// <remarks>This type was introduced to make <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncClass{TInput,TResult})"/>
/// work without clashing with <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncStruct{TInput,TResult})"/>.</remarks>
public delegate TResult FuncClass<in TInput, TResult>(TInput input) where TResult : class;

/// <summary>Provides extension methods that apply to all types.</summary>
public static class ObjectExtensions
{
    /// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
    /// <typeparam name="TInput">Type of the input value.</typeparam>
    /// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
    /// <param name="input">Input value to check for null.</param>
    /// <param name="lambda">Function to apply the input value to if it is not null.</param>
    public static TResult NullOr<TInput, TResult>(this TInput input, FuncClass<TInput, TResult> lambda) where TResult : class
    {
        return input == null ? null : lambda(input);
    }

    /// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
    /// <typeparam name="TInput">Type of the input value.</typeparam>
    /// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
    /// <param name="input">Input value to check for null.</param>
    /// <param name="lambda">Function to apply the input value to if it is not null.</param>
    public static TResult? NullOr<TInput, TResult>(this TInput input, Func<TInput, TResult?> lambda) where TResult : struct
    {
        return input == null ? null : lambda(input);
    }

    /// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
    /// <typeparam name="TInput">Type of the input value.</typeparam>
    /// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
    /// <param name="input">Input value to check for null.</param>
    /// <param name="lambda">Function to apply the input value to if it is not null.</param>
    public static TResult? NullOr<TInput, TResult>(this TInput input, FuncStruct<TInput, TResult> lambda) where TResult : struct
    {
        return input == null ? null : lambda(input).Nullable();
    }
}

Tak, to jest bardziej ogólna odpowiedź na zamierzony problem - pokonałeś mnie w tym - i kandydat do bezpiecznej nawigacji (jeśli nie masz nic przeciwko lambda dla prostych rzeczy) - ale nadal jest to trochę kłopotliwe, dobrze :). Osobiście zawsze bym wybierał? : zamiast tego (jeśli nie drogie, jeśli to i tak to przestawić) ...
— NSGaga-głównie-nieaktywny

... A 'Nazewnictwo' jest prawdziwym problemem z tym - nic tak naprawdę nie wydaje się przedstawiać dobrze (ani 'dodaje' za dużo) lub jest długie - NullOr jest dobre, ale zbyt duży nacisk na 'zerowy' IMO (plus ty have ?? still) - użyłem „Property” lub „Safe”. value.Dot (o => o.property) ?? @default może?
— NSGaga-głównie-nieaktywny

@NSGaga: Od dłuższego czasu pracowaliśmy nad nazwą. DotRozważaliśmy, ale uznaliśmy, że jest to zbyt nieopisowe. Zdecydowaliśmy się na NullOrdobry kompromis między samowyjaśnieniem a zwięzłością. Jeśli naprawdę nie dbałeś o nazewnictwo, zawsze możesz to nazwać _. Jeśli uznasz, że lambdy są zbyt kłopotliwe do napisania, możesz użyć do tego fragmentu kodu, ale osobiście uważam to za dość łatwe. Co do tego ? :, nie możesz tego używać z bardziej złożonymi wyrażeniami, musiałbyś przenieść je do nowego lokalnego; NullOrpozwala tego uniknąć.
— Timwi

6

Wolałbym jednorazowo użyć bezpiecznego rzutowania na łańcuch na wypadek, gdyby obiekt przechowywany z kluczem nie był jednym. Używanie ToString()może nie przynieść oczekiwanych rezultatów.

var y = Session["key"] as string ?? "none";

Jak mówi @Jon Skeet, jeśli często robisz to jako metoda rozszerzająca lub, jeszcze lepiej, może metoda rozszerzająca w połączeniu z silnie wpisaną klasą SessionWrapper. Nawet bez metody rozszerzającej opakowanie o jednoznacznie określonym typie może być dobrym pomysłem.

public class SessionWrapper
{
    private HttpSessionBase Session { get; set; }

    public SessionWrapper( HttpSessionBase session )
    {
        Session = session;
    }

    public SessionWrapper() : this( HttpContext.Current.Session ) { }

    public string Key
    {
         get { return Session["key"] as string ?? "none";
    }

    public int MaxAllowed
    {
         get { return Session["maxAllowed"] as int? ?? 10 }
    }
}

Użyty jako

 var session = new SessionWrapper(Session);

 string key = session.Key;
 int maxAllowed = session.maxAllowed;

3

stworzyć funkcję pomocniczą

public static String GetValue( string key, string default )
{
    if ( Session[ key ] == null ) { return default; }
    return Session[ key ].toString();
}


string y = GetValue( 'key', 'none' );

2

Odpowiedź Skeeta jest najlepsza - szczególnie uważam, że jego ToStringOrNull()jest dość elegancki i najlepiej pasuje do twoich potrzeb. Chciałem dodać jeszcze jedną opcję do listy metod rozszerzających:

Zwróć oryginalny obiekt lub domyślną wartość ciągu dla null :

// Method:
public static object OrNullAsString(this object input, string defaultValue)
{
    if (defaultValue == null)
        throw new ArgumentNullException("defaultValue");
    return input == null ? defaultValue : input;
}

// Example:
var y = Session["key"].OrNullAsString("defaultValue");

Użyj vardla zwróconej wartości, ponieważ wróci jako typ oryginalnego wejścia, tylko jako ciąg domyślny, kiedynull


Po co rzucać wyjątek, null defaultValuejeśli nie jest potrzebny (to znaczy input != null)?
— Attila

input != nullEval zwróci przedmiot jako siebie. input == nullzwraca ciąg podany jako parametr. dlatego jest możliwe, że osoba mogłaby zadzwonić .OnNullAsString(null)- ale celem (choć rzadko użyteczna metoda rozszerzenia) było zapewnienie albo odzyskania obiektu z powrotem, albo domyślnego ciągu ... nigdy null
— one.beat.consumer

input!=nullScenariusz zwróci jedynie wejście czy defaultValue!=nullteż posiada; w przeciwnym razie wyrzuci plik ArgumentNullException.
— Attila

0

To jest mój mały "operator Elvisa" bezpieczny dla typów dla wersji .NET, które nie obsługują?.

public class IsNull
{
    public static O Substitute<I,O>(I obj, Func<I,O> fn, O nullValue=default(O))
    {
        if (obj == null)
            return nullValue;
        else
            return fn(obj);
    }
}

Pierwszym argumentem jest testowany obiekt. Druga to funkcja. A trzecia to wartość zerowa. Więc w twoim przypadku:

IsNull.Substitute(Session["key"],s=>s.ToString(),"none");

Jest to również bardzo przydatne w przypadku typów dopuszczających wartość null. Na przykład:

decimal? v;
...
IsNull.Substitute(v,v.Value,0);
....
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.