Was ist der richtige Weg, um nach Nullwerten zu suchen?


122

Ich liebe den Null-Koaleszenz-Operator, weil er es einfach macht, einen Standardwert für nullfähige Typen zuzuweisen.

 int y = x ?? -1;

Das ist großartig, außer wenn ich etwas Einfaches tun muss x. Wenn ich zum Beispiel nachsehen möchte, Sessionmuss ich normalerweise etwas ausführlicheres schreiben.

Ich wünschte, ich könnte das tun:

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

Dies ist jedoch nicht möglich, da der .ToString()Aufruf vor der Nullprüfung erfolgt und daher fehlschlägt, wenn er Session["key"]null ist. Am Ende mache ich das:

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

Es funktioniert und ist meiner Meinung nach besser als die dreizeilige Alternative:

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

Auch wenn das funktioniert, bin ich immer noch gespannt, ob es einen besseren Weg gibt. Es scheint egal, worauf ich mich immer Session["key"]zweimal beziehen muss ; einmal für die Prüfung und noch einmal für die Zuordnung. Irgendwelche Ideen?


20
Zu diesem Zeitpunkt wünschte ich mir, C # hätte einen "sicheren Navigationsoperator" ( .?) wie Groovy .
Cameron

2
@Cameron: In diesem Fall wünschte ich mir, C # könnte nullbare Typen (einschließlich Referenztypen) als Monade behandeln, sodass Sie keinen „sicheren Navigationsoperator“ benötigen.
Jon Purdy

3
Der Erfinder der Nullreferenzen nannte es seinen "Milliarden-Dollar-Fehler", und ich stimme eher zu. Siehe infoq.com/presentations/…
Jamie Ide

Sein eigentlicher Fehler ist das unsichere (nicht sprachgesteuerte) Mischen von nullbaren und nicht nullbaren Typen.
MSalters

@ JamieIde Danke für einen sehr interessanten Link. :)
BobRodes

Antworten:


182

Wie wäre es mit

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

79
Die Kraft ist stark mit diesem.
Chev

2
@ Matthew: Nein, weil Sitzungswerte vom Typ Object
BlackBear

1
@BlackBear, aber der zurückgegebene Wert ist höchstwahrscheinlich eine Zeichenfolge, daher ist die Besetzung gültig
Firo

Dies war die direkteste Antwort auf meine Frage, daher markiere ich die Antwort, aber die Erweiterungsmethode von Jon Skeet .ToStringOrDefault()ist meine bevorzugte Methode. Ich verwende diese Antwort jedoch innerhalb von Jons Erweiterungsmethode;)
Chev

10
Ich mag das nicht, denn wenn Sie eine andere Art von Objekt in der Sitzung haben als erwartet, verstecken Sie möglicherweise einige subtile Fehler in Ihrem Programm. Ich würde lieber eine sichere Besetzung verwenden, da ich denke, dass Fehler wahrscheinlich schneller auftauchen. Außerdem wird vermieden, dass ToString () für ein Zeichenfolgenobjekt aufgerufen wird.
Tvanfosson

130

Wenn Sie dies häufig speziell mitToString() tun, können Sie eine Erweiterungsmethode schreiben:

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

...

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

Oder natürlich eine Standardmethode:

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

...

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

16
.ToStringOrDefault()ist einfach und elegant. Eine schöne Lösung.
Chev

7
Dem kann ich überhaupt nicht zustimmen. Erweiterungsmethoden objectsind ein Fluch und machen eine Codebasis kaputt, und Erweiterungsmethoden, die fehlerfrei mit Nullwerten arbeiten this, sind absolut böse.
Nick Larsen

10
@ NickLarsen: Alles in Maßen, sage ich. Erweiterungsmethoden, die mit null arbeiten, können sehr nützlich sein, IMO - solange klar ist, was sie tun.
Jon Skeet

3
@ one.beat.consumer: Ja. Wenn es nur die Formatierung (oder irgendwelche Tippfehler) gewesen wäre, wäre das eine Sache gewesen, aber das Ändern der Wahl des Methodennamens eines Autors geht über das hinaus, was normalerweise für die Bearbeitung angemessen ist, IMO.
Jon Skeet

6
@ one.beat.consumer: Wenn ich Grammatik und Tippfehler korrigiere, ist das in Ordnung - aber das Ändern eines Namens, den jemand (jeder, nicht nur ich) absichtlich gewählt hat, fühlt sich für mich anders an. An dieser Stelle würde ich es stattdessen in einem Kommentar vorschlagen.
Jon Skeet

21

Sie können auch verwenden as, was ergibt, nullwenn die Konvertierung fehlschlägt:

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

Dies würde zurückkehren , "none"selbst wenn jemand einen ausgestopften intin Session["key"].


1
Dies funktioniert nur, wenn Sie es überhaupt nicht brauchen würden ToString().
Abel

1
Ich bin überrascht, dass noch niemand diese Antwort abgelehnt hat. Dies unterscheidet sich semantisch völlig von dem, was das OP tun möchte.
Timwi

@ Timwi: Das OP ToString()konvertiert ein Objekt, das eine Zeichenfolge enthält, in eine Zeichenfolge. Sie können dasselbe mit obj as stringoder tun (string)obj. Es ist eine ziemlich häufige Situation in ASP.NET.
Andomar

5
@Andomar: Nein, das OP ruft ToString()ein Objekt (nämlich Session["key"]) auf, dessen Typ er nicht erwähnt hat. Es kann sich um jede Art von Objekt handeln, nicht unbedingt um eine Zeichenfolge.
Timwi

13

Wenn es immer ein sein wird string, kannst du Folgendes besetzen:

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

Dies hat den Vorteil, sich zu beschweren, anstatt den Fehler zu verbergen, wenn jemand einen intoder etwas hineinsteckt Session["key"]. ;)


10

Alle vorgeschlagenen Lösungen sind gut und beantworten die Frage. das ist also nur um es etwas zu verlängern. Derzeit befassen sich die meisten Antworten nur mit Nullvalidierung und Zeichenfolgentypen. Sie können das StateBagObjekt um eine generische GetValueOrDefaultMethode erweitern, ähnlich der Antwort von Jon Skeet.

Eine einfache generische Erweiterungsmethode, die eine Zeichenfolge als Schlüssel akzeptiert und dann vom Typ das Sitzungsobjekt überprüft. Wenn das Objekt null oder nicht vom gleichen Typ ist, wird der Standardwert zurückgegeben, andernfalls wird der Sitzungswert stark typisiert zurückgegeben.

Etwas wie das

/// <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
Können Sie ein Verwendungsbeispiel für diese Erweiterungsmethode hinzufügen? Behandelt StateBag nicht den Ansichtsstatus und nicht die Sitzung? Ich verwende ASP.NET MVC 3, daher habe ich keinen einfachen Zugriff auf den Anzeigestatus. Ich denke du willst verlängern HttpSessionState.
Chev

Für diese Antwort muss der Wert 3x und 2 Casts abgerufen werden, wenn dies erfolgreich ist. (Ich weiß, es ist ein Wörterbuch, aber Anfänger können ähnliche Praktiken bei teuren Methoden
Jake Berger

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

1
@jberger Das Umwandeln in einen Wert mit "as" ist nicht möglich, da für den generischen Typ keine Klassenbeschränkung besteht, da Sie möglicherweise einen Wert wie bool zurückgeben möchten. @AlexFord Ich entschuldige mich, Sie möchten die HttpSessionStatefür die Sitzung verlängern. :)
Richard

tatsächlich. Wie Richard feststellte, erfordert die Einschränkung. (... und eine andere Methode, wenn Sie Werttypen verwenden möchten)
Jake Berger

7

Wir verwenden eine Methode namens NullOr.

Verwendung

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

Quelle

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

Ja, dies ist die allgemeinere Antwort auf das beabsichtigte Problem - Sie haben mich geschlagen - und ein Kandidat für eine sichere Navigation (wenn Ihnen die Lambda-s für einfache Dinge nichts ausmachen) - aber das Schreiben ist immer noch etwas umständlich. Gut :). Persönlich würde ich immer die auswählen? : stattdessen (wenn nicht teuer, wenn es dann sowieso neu angeordnet wird) ...
NSGaga-meistens-inaktiv

... Und das 'Benennen' ist das eigentliche Problem bei diesem - nichts scheint wirklich richtig darzustellen (oder 'fügt' zu viel hinzu) oder ist lang - NullOr ist gut, aber zu viel Wert auf 'null' IMO (plus Sie) habe ?? noch) - 'Property' oder 'Safe' habe ich benutzt. value.Dot (o => o.property) ?? @ Default vielleicht?
NSGaga-meistens-inaktiv

@NSGaga: Wir haben uns einige Zeit mit dem Namen beschäftigt. Wir haben es in Betracht gezogen Dot, fanden es aber zu unbeschreiblich. Wir haben uns für NullOreinen guten Kompromiss zwischen Selbsterklärung und Kürze entschieden. Wenn Ihnen die Benennung überhaupt nicht wichtig war, können Sie sie jederzeit anrufen _. Wenn Sie die Lambdas zu umständlich zum Schreiben finden, können Sie dafür einen Ausschnitt verwenden, aber ich persönlich finde es ziemlich einfach genug. Was ? :können Sie nicht verwenden , die mit komplexeren Ausdrücken, würden Sie sie zu einem neuen lokalen bewegen müssen; NullOrermöglicht es Ihnen, dies zu vermeiden.
Timwi

6

Ich würde es vorziehen, eine sichere Umwandlung für eine Zeichenfolge zu verwenden, falls das mit dem Schlüssel gespeicherte Objekt keine ist. Die Verwendung führt ToString()möglicherweise nicht zu den gewünschten Ergebnissen.

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

Wie @Jon Skeet sagt, wenn Sie dies häufig tun, handelt es sich um eine Erweiterungsmethode oder besser noch um eine Erweiterungsmethode in Verbindung mit einer stark typisierten SessionWrapper-Klasse. Auch ohne die Erweiterungsmethode ist der stark typisierte Wrapper möglicherweise eine gute Idee.

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

Benutzt als

 var session = new SessionWrapper(Session);

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

3

Erstellen Sie eine Hilfsfunktion

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


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

2

Skeets Antwort ist die beste - insbesondere finde ich, dass seine ToStringOrNull()ziemlich elegant ist und am besten zu Ihren Bedürfnissen passt. Ich wollte der Liste der Erweiterungsmethoden eine weitere Option hinzufügen:

Rückgabe des ursprünglichen Objekts oder des Standardzeichenfolgenwerts für 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");

Verwenden Sie diesen varWert für den zurückgegebenen Wert, da er nur als Standardzeichenfolge zurückgegeben wird, wennnull


Warum eine Ausnahme auslösen, null defaultValuewenn sie nicht benötigt wird input != null?
Attila

Eine input != nullAuswertung gibt das Objekt als sich selbst zurück. input == nullGibt die als Parameter angegebene Zeichenfolge zurück. Daher ist es möglich, dass eine Person aufruft .OnNullAsString(null)- aber der Zweck (wenn auch selten nützliche Erweiterungsmethode) bestand darin, sicherzustellen, dass Sie entweder das Objekt oder die Standardzeichenfolge
zurückerhalten

Das input!=nullSzenario gibt die Eingabe nur zurück, wenn dies defaultValue!=nullauch gilt. sonst wird es ein werfen ArgumentNullException.
Attila

0

Dies ist mein kleiner typsicherer "Elvis-Operator" für Versionen von .NET, die nicht unterstützen?

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

Das erste Argument ist das getestete Objekt. Zweitens ist die Funktion. Und drittens ist der Nullwert. Also für Ihren Fall:

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

Dies ist auch für nullfähige Typen sehr nützlich. Beispielsweise:

decimal? v;
...
IsNull.Substitute(v,v.Value,0);
....
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.