Quelle est la bonne façon de vérifier les valeurs nulles?

122

J'adore l'opérateur null-coalescing car il facilite l'attribution d'une valeur par défaut pour les types Nullable.

 int y = x ?? -1;

C'est génial, sauf si j'ai besoin de faire quelque chose de simple avec x. Par exemple, si je veux vérifier Session, je finis généralement par devoir écrire quelque chose de plus détaillé.

J'aimerais pouvoir faire ceci:

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

Mais vous ne pouvez pas car le .ToString()est appelé avant la vérification de null, donc il échoue s'il Session["key"]est nul. Je finis par faire ceci:

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

Cela fonctionne et c'est mieux, à mon avis, que l'alternative à trois lignes:

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

Même si cela fonctionne, je suis toujours curieux de savoir s'il existe un meilleur moyen. Il semble que peu importe ce que je dois toujours mentionner Session["key"]deux fois; une fois pour le chèque, et encore une fois pour la mission. Des idées?

Chev
la source
20
C'est à ce moment que je souhaite que C # ait un "opérateur de navigation sûr" ( .?) comme Groovy .
Cameron
2
@Cameron: C'est à ce moment que je souhaite que C # puisse traiter les types Nullable (y compris les types de référence) comme une monade, donc vous n'avez pas besoin d'un «opérateur de navigation sûre».
Jon Purdy
3
L'inventeur des références nulles l'appelait son «erreur d'un milliard de dollars» et j'ai tendance à être d'accord. Voir infoq.com/presentations/…
Jamie Ide
Son erreur réelle est le mélange dangereux (non imposé par le langage) de types Nullable et non Nullabel.
MSalters
@JamieIde Merci pour un lien très intéressant. :)
BobRodes

Réponses:

182

Qu'en est-il de

string y = (Session["key"] ?? "none").ToString();
Ours noir
la source
79
La force est forte avec celui-ci.
Chev
2
@Matthew: Non car les valeurs de session sont de type Object
BlackBear
1
@BlackBear mais la valeur retournée est très probablement une chaîne, donc la distribution est valide
Firo
C'était la réponse la plus directe à ma question, donc je marque la réponse, mais la méthode d'extension de Jon Skeet .ToStringOrDefault()est ma façon préférée de le faire. Cependant, j'utilise cette réponse dans la méthode d'extension de Jon;)
Chev
10
Je n'aime pas cela parce que si vous avez un autre type d'objet dans la session que ce à quoi vous vous attendez, vous risquez de cacher des bogues subtils dans votre programme. Je préfère utiliser un casting sûr car je pense qu'il est susceptible de faire apparaître des erreurs plus rapidement. Cela évite également d'appeler ToString () sur un objet chaîne.
tvanfosson
130

Si vous faites souvent cela spécifiquement avec,ToString() vous pouvez écrire une méthode d'extension:

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

...

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

Ou une méthode prenant un défaut, bien sûr:

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

...

string y = Session["key"].ToStringOrDefault("none");
Jon Skeet
la source
16
.ToStringOrDefault()est simple et élégant. Une belle solution.
Chev
7
Je ne peux pas du tout être d'accord avec cela. Les méthodes d'extension sur objectsont une malédiction et des ordures dans une base de code, et les méthodes d'extension qui fonctionnent sans erreur sur des thisvaleurs nulles sont du mal pur.
Nick Larsen
10
@NickLarsen: Tout avec modération, dis-je. Les méthodes d'extension qui fonctionnent avec null peuvent être très utiles, IMO - tant qu'elles sont claires sur ce qu'elles font.
Jon Skeet
3
@ one.beat.consumer: Ouais. S'il ne s'agissait que du formatage (ou des fautes de frappe), cela aurait été une chose, mais changer le choix du nom de la méthode par un auteur va au-delà de ce qui est normalement une édition appropriée, IMO.
Jon Skeet
6
@ one.beat.consumer: Lors de la correction de la grammaire et des fautes de frappe, c'est bien - mais changer un nom que quelqu'un (n'importe qui, pas seulement moi) a clairement choisi délibérément me semble différent. À ce stade, je le suggérerais plutôt dans un commentaire.
Jon Skeet
21

Vous pouvez également utiliser as, qui donne nullsi la conversion échoue:

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

Cela reviendrait "none"même si quelqu'un bourrait unint dans Session["key"].

Andomar
la source
1
Cela ne fonctionne que lorsque vous n'en avez pas besoin ToString()en premier lieu.
Abel
1
Je suis surpris que personne n'ait encore voté contre cette réponse. Ceci est sémantiquement complètement différent de ce que l'OP veut faire.
Timwi
@Timwi: l'OP utilise ToString()pour convertir un objet contenant une chaîne en chaîne. Vous pouvez faire de même avec obj as stringou (string)obj. C'est une situation assez courante dans ASP.NET.
Andomar
5
@Andomar: Non, l'OP appelle ToString()un objet (à savoir, Session["key"]) dont il n'a pas mentionné le type. Cela peut être n'importe quel type d'objet, pas nécessairement une chaîne.
Timwi
13

Si ce sera toujours un string, vous pouvez lancer:

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

Cela a l'avantage de se plaindre au lieu de cacher l'erreur si quelqu'un fourre un intou quelque chose Session["key"]. ;)

Ry-
la source
10

Toutes les solutions suggérées sont bonnes et répondent à la question; donc c'est juste pour étendre légèrement dessus. Actuellement, la majorité des réponses ne concernent que la validation nulle et les types de chaîne. Vous pouvez étendre l' StateBagobjet pour inclure une GetValueOrDefaultméthode générique , similaire à la réponse publiée par Jon Skeet.

Une méthode d'extension générique simple qui accepte une chaîne comme clé, puis vérifie le type de l'objet de session. Si l'objet est nul ou n'est pas du même type, la valeur par défaut est renvoyée, sinon la valeur de session est renvoyée fortement typée.

Quelque chose comme ça

/// <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;
}
Richard
la source
1
Pouvez-vous inclure un exemple d'utilisation pour cette méthode d'extension? StateBag ne gère-t-il pas l'état de la vue et non la session? J'utilise ASP.NET MVC 3 donc je n'ai pas vraiment un accès simple pour afficher l'état. Je pense que tu veux prolonger HttpSessionState.
Chev
cette réponse nécessite de récupérer la valeur 3x et 2 casts si elle réussit. (Je sais que c'est un dictionnaire, mais les débutants peuvent utiliser des pratiques similaires sur des méthodes coûteuses.)
Jake Berger
3
T value = source[key] as T; return value ?? defaultValue;
Jake Berger
1
@jberger La conversion en valeur à l'aide de "as" est inaccessible car il n'y a pas de contrainte de classe sur le type générique car vous voudrez peut-être renvoyer une valeur telle que bool. @AlexFord Mes excuses, vous voudriez prolonger HttpSessionStatela session. :)
Richard
En effet. comme l'a noté Richard, exige la contrainte. (... et une autre méthode si vous souhaitez utiliser des types de valeur)
Jake Berger
7

Nous utilisons une méthode appelée NullOr .

Usage

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

La source

/// <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();
    }
}
Timwi
la source
Oui, c'est la réponse la plus générique au problème prévue - vous m'avez battu - et un candidat pour une navigation sûre (si cela ne vous dérange pas les lambda-s pour des choses simples) - mais c'est toujours un peu difficile à écrire, bien :). Personnellement, je choisirais toujours le? : à la place (si ce n'est pas cher, si c'est alors réorganiser de toute façon) ...
NSGaga-la plupart du temps-inactif
... Et le `` Naming '' est le vrai problème avec celui-ci - rien ne semble vraiment représenter correctement (ou `` ajoute '' trop), ou est long - NullOr est bien mais trop d'accent sur `` null '' IMO (plus vous avoir ?? encore) - «Propriété», ou «Safe» est ce que j'ai utilisé. value.Dot (o => o.property) ?? @ par défaut peut-être?
NSGaga-most-inactive
@NSGaga: Nous avons fait des allers-retours sur le nom pendant un certain temps. Nous l'avons considéré, Dotmais nous l'avons trouvé trop peu descriptif. Nous avons opté pour NullOrun bon compromis entre l'auto-explication et la brièveté. Si vous ne vous souciez vraiment pas du nom du nom, vous pouvez toujours l'appeler _. Si vous trouvez que les lambdas sont trop compliquées à écrire, vous pouvez utiliser un extrait de code pour cela, mais personnellement, je trouve cela assez facile. Quant à ? :, vous ne pouvez pas utiliser cela avec des expressions plus complexes, vous devez les déplacer vers un nouveau local; NullOrvous permet d'éviter cela.
Timwi
6

Ma préférence, pour un one off, serait d'utiliser un cast sécurisé en chaîne au cas où l'objet stocké avec la clé n'en serait pas un. L'utilisation ToString()peut ne pas avoir les résultats souhaités.

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

Comme le dit @Jon Skeet, si vous vous trouvez souvent en train de faire cela, une méthode d'extension ou, mieux, peut-être une méthode d'extension en conjonction avec une classe SessionWrapper fortement typée. Même sans la méthode d'extension, le wrapper fortement typé peut être une bonne idée.

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

Utilisé comme

 var session = new SessionWrapper(Session);

 string key = session.Key;
 int maxAllowed = session.maxAllowed;
Tvanfosson
la source
3

créer une fonction auxiliaire

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


string y = GetValue( 'key', 'none' );
scibuff
la source
2

La réponse de Skeet est la meilleure - en particulier, je pense que la sienne ToStringOrNull()est assez élégante et répond le mieux à vos besoins. Je voulais ajouter une autre option à la liste des méthodes d'extension:

Renvoie l'objet d'origine ou la valeur de chaîne par défaut pour 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");

À utiliser varpour la valeur renvoyée car elle reviendra comme type d'entrée d'origine, uniquement comme chaîne par défaut lorsquenull

one.beat.consumer
la source
Pourquoi lancer une exception null defaultValuesi elle n'est pas nécessaire (c'est-à-dire input != null)?
Attila
Un input != nulleval renverra l'objet comme lui-même. input == nullrenvoie la chaîne fournie en tant que paramètre. par conséquent, il est possible qu'une personne puisse appeler .OnNullAsString(null)- mais le but (bien que rarement une méthode d'extension utile) était de s'assurer que vous
récupériez
Le input!=nullscénario ne renverra l'entrée que s'il defaultValue!=nullest également valide; sinon, il lancera un fichier ArgumentNullException.
Attila
0

Ceci est mon petit "opérateur Elvis" de type sécurisé pour les versions de .NET qui ne prennent pas en charge?.

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

Le premier argument est l'objet testé. La deuxième est la fonction. Et le troisième est la valeur nulle. Donc pour votre cas:

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

Il est également très utile pour les types Nullable. Par exemple:

decimal? v;
...
IsNull.Substitute(v,v.Value,0);
....
Tomaz Stih
la source