Qual è il modo corretto per verificare la presenza di valori nulli?


122

Adoro l'operatore di coalescenza null perché semplifica l'assegnazione di un valore predefinito per i tipi nullable.

 int y = x ?? -1;

È fantastico, tranne se devo fare qualcosa di semplice con x. Ad esempio, se voglio controllare Session, di solito finisco per dover scrivere qualcosa di più dettagliato.

Vorrei poterlo fare:

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

Ma non puoi perché .ToString()viene chiamato prima del controllo null, quindi non riesce se Session["key"]è null. Finisco per fare questo:

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

Funziona ed è migliore, secondo me, dell'alternativa a tre righe:

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

Anche se funziona, sono ancora curioso di sapere se esiste un modo migliore. Non importa a cosa devo sempre fare riferimento Session["key"]due volte; una volta per il controllo e di nuovo per l'assegnazione. Qualche idea?


20
Questo è quando vorrei che C # avesse un "operatore di navigazione sicuro" ( .?) come Groovy ha .
Cameron

2
@Cameron: questo è il momento in cui vorrei che C # potesse trattare i tipi nullable (inclusi i tipi di riferimento) come una monade, quindi non avresti bisogno di un "operatore di navigazione sicuro".
Jon Purdy

3
L'inventore di riferimenti nulli lo ha definito il suo "errore da un miliardo di dollari" e io tendo ad essere d'accordo. Vedi infoq.com/presentations/…
Jamie Ide

Il suo vero errore è la combinazione non sicura (non applicata dalla lingua) di tipi nullable e non nullabel.
MSalters

@ JamieIde Grazie per un collegamento molto interessante. :)
BobRodes

Risposte:


182

Che dire

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

79
La forza è forte con questo.
Chev

2
@ Matthew: No perché i valori della sessione sono di tipo Object
BlackBear

1
@BlackBear ma il valore restituito è molto probabilmente una stringa, quindi il cast è valido
Firo

Questa è stata la risposta più diretta alla mia domanda, quindi sto contrassegnando la risposta, ma il metodo di estensione di Jon Skeet .ToStringOrDefault()è il mio modo preferito per farlo. Tuttavia, sto usando questa risposta all'interno del metodo di estensione di Jon;)
Chev

10
Non mi piace perché se hai un qualsiasi altro tipo di oggetto riempito nella sessione rispetto a quello che ti aspetti, potresti nascondere alcuni bug sottili nel tuo programma. Preferisco usare un cast sicuro perché penso che sia probabile che emerga più velocemente gli errori. Inoltre evita di chiamare ToString () su un oggetto stringa.
tvanfosson

130

Se lo fai spesso in modo specifico conToString() potresti scrivere un metodo di estensione:

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

...

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

O un metodo che assume un valore predefinito, ovviamente:

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

...

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

16
.ToStringOrDefault()è semplice ed elegante. Una bella soluzione.
Chev

7
Non posso assolutamente essere d'accordo con questo. I metodi di estensione su objectsono una maledizione e spazzatura su una base di codice, mentre i metodi di estensione che funzionano senza errori su thisvalori nulli sono puro male.
Nick Larsen

10
@ NickLarsen: tutto con moderazione, dico. I metodi di estensione che funzionano con null possono essere molto utili, IMO, purché siano chiari su ciò che fanno.
Jon Skeet

3
@ one.beat.consumer: Sì. Se fosse stata solo la formattazione (o eventuali errori di battitura), quella sarebbe stata una cosa, ma cambiare la scelta di un autore del nome del metodo va oltre ciò che è normalmente appropriato, IMO.
Jon Skeet

6
@ one.beat.consumer: Quando si corregge la grammatica e gli errori di battitura, va bene, ma cambiare un nome che qualcuno (chiunque, non solo io) ha scelto deliberatamente mi sembra diverso. A quel punto lo suggerirei invece in un commento.
Jon Skeet

21

Puoi anche usare as, che restituisce nullse la conversione fallisce:

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

Questo sarebbe tornato "none"anche se qualcuno farcito una intin Session["key"].


1
Funziona solo quando non ne avresti bisogno ToString()in primo luogo.
Abel

1
Sono sorpreso che nessuno abbia ancora votato negativamente questa risposta. Questo è semanticamente completamente diverso da ciò che l'OP vuole fare.
Timwi

@Timwi: L'OP utilizza ToString()per convertire un oggetto contenente una stringa in una stringa. Puoi fare lo stesso con obj as stringo (string)obj. È una situazione abbastanza comune in ASP.NET.
Andomar

5
@Andomar: No, l'OP sta chiamando ToString()un oggetto (vale a dire, Session["key"]) di cui non ha menzionato il tipo. Potrebbe essere qualsiasi tipo di oggetto, non necessariamente una stringa.
Timwi

13

Se sarà sempre a string, puoi trasmettere :

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

Questo ha il vantaggio di lamentarsi invece di nascondere l'errore se qualcuno inserisce un into qualcosa Session["key"]. ;)


10

Tutte le soluzioni suggerite sono valide e rispondono alla domanda; quindi questo è solo per estenderlo leggermente. Attualmente la maggior parte delle risposte riguarda solo la convalida nulla e i tipi di stringa. È possibile estendere l' StateBagoggetto per includere un genericoGetValueOrDefault metodo , simile alla risposta pubblicata da Jon Skeet.

Un semplice metodo di estensione generico che accetta una stringa come chiave e quindi controlla il tipo dell'oggetto sessione. Se l'oggetto è null o non è dello stesso tipo, viene restituito il valore predefinito, altrimenti il ​​valore della sessione viene restituito fortemente tipizzato.

Qualcosa come questo

/// <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
Potete includere un esempio di utilizzo per questo metodo di estensione? StateBag non si occupa dello stato di visualizzazione e non della sessione? Sto usando ASP.NET MVC 3 quindi non ho un accesso semplice per visualizzare lo stato. Penso che tu voglia estendere HttpSessionState.
Chev

questa risposta richiede il recupero del valore 3x e 2 cast se riesce. (So ​​che è un dizionario, ma i principianti possono usare pratiche simili su metodi costosi.)
Jake Berger

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

1
@jberger Casting to value utilizzando "as" è inaccessibile in quanto non esiste un vincolo di classe sul tipo generico poiché potenzialmente potresti voler restituire un valore come bool. @AlexFord Le mie scuse, vorresti estendere HttpSessionStatela sessione. :)
Richard

infatti. come ha notato Richard, richiede il vincolo. (... e un altro metodo se si desidera utilizzare i tipi di valore)
Jake Berger

7

Usiamo un metodo chiamato NullOr .

uso

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

fonte

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

Sì, questa è la risposta più generica al problema inteso - mi hai battuto - e un candidato per una navigazione sicura (se non ti dispiace i lambda per cose semplici) - ma è ancora un po 'macchinoso da scrivere, bene :). Personalmente sceglierei sempre il? : invece (se non costoso, se è poi riorganizzare comunque) ...
NSGaga-per lo più inattivo

... E il 'Naming' è il vero problema con questo - niente sembra davvero rappresentare bene (o 'aggiunge' troppo), o è lungo - NullOr è buono ma troppa enfasi su 'null' IMO (più tu have ?? still) - "Proprietà" o "Sicuro" è ciò che ho usato. value.Dot (o => o.property) ?? @default forse?
NSGaga - per lo più inattivo

@NSGaga: Siamo andati avanti e indietro sul nome per un bel po 'di tempo. Lo abbiamo considerato Dotma lo abbiamo trovato troppo poco descrittivo. Abbiamo optato per NullOrun buon compromesso tra auto-spiegazione e brevità. Se davvero non ti importava affatto del nome, potresti sempre chiamarlo _. Se trovi i lambda troppo ingombranti da scrivere, puoi usare uno snippet per questo, ma personalmente lo trovo abbastanza facile. Per quanto riguarda ? :, non puoi usarlo con espressioni più complesse, dovresti spostarle in un nuovo locale; NullOrti permette di evitarlo.
Timwi

6

La mia preferenza, per uno, sarebbe quella di utilizzare un cast sicuro per stringere nel caso in cui l'oggetto memorizzato con la chiave non sia uno. L'utilizzo ToString()potrebbe non avere i risultati desiderati.

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

Come dice @ Jon Skeet, se ti ritrovi a farlo spesso come un metodo di estensione o, meglio, forse un metodo di estensione in combinazione con una classe SessionWrapper fortemente tipizzata. Anche senza il metodo di estensione, il wrapper fortemente tipizzato potrebbe essere una buona idea.

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

Usato come

 var session = new SessionWrapper(Session);

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

3

creare una funzione ausiliaria

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


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

2

La risposta di Skeet è la migliore, in particolare penso che la sua ToStringOrNull()sia piuttosto elegante e si adatti meglio alle tue esigenze. Volevo aggiungere un'altra opzione all'elenco dei metodi di estensione:

Restituisce l'oggetto originale o il valore di stringa predefinito per 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");

Utilizzare varper il valore restituito poiché tornerà come tipo di input originale, solo come stringa predefinita quandonull


Perché lanciare un'eccezione null defaultValuese non è necessaria (cioè input != null)?
Attila

Un input != nulleval restituirà l'oggetto come se stesso. input == nullrestituisce la stringa fornita come parametro. quindi è possibile che una persona possa chiamare .OnNullAsString(null), ma lo scopo (anche se il metodo di estensione raramente utile) era quello di assicurarsi di recuperare l'oggetto o la stringa predefinita ... mai null
one.beat.consumer

Lo input!=nullscenario restituirà l'input solo se defaultValue!=nullè valido; altrimenti lancerà un ArgumentNullException.
Attila

0

Questo è il mio piccolo "operatore Elvis" sicuro per le versioni di .NET che non supportano?.

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

Il primo argomento è l'oggetto testato. La seconda è la funzione. E il terzo è il valore nullo. Quindi per il tuo caso:

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

È molto utile anche per i tipi nullable. Per esempio:

decimal? v;
...
IsNull.Substitute(v,v.Value,0);
....
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.