Qual é a maneira correta de verificar valores nulos?

122

Adoro o operador de coalescência nula porque facilita a atribuição de um valor padrão para tipos anuláveis.

 int y = x ?? -1;

Isso é ótimo, exceto se eu precisar fazer algo simples x. Por exemplo, se eu quiser verificar Session, geralmente acabo tendo que escrever algo mais detalhado.

Eu gostaria de poder fazer isso:

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

Mas você não pode, porque a .ToString()chamada é feita antes da verificação nula, portanto ela falha se Session["key"]for nula. Acabo fazendo isso:

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

Funciona e é melhor, na minha opinião, do que a alternativa de três linhas:

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

Mesmo que isso funcione, ainda estou curioso para saber se existe uma maneira melhor. Parece que não importa o que eu sempre precise fazer referência Session["key"]duas vezes; uma vez para o cheque e novamente para a tarefa. Alguma ideia?

Chev
fonte
20
É aí que eu gostaria que o C # tivesse um "operador de navegação segura" ( .?) como o Groovy .
Cameron
2
@Ameron: é quando desejo que o c # possa tratar tipos anuláveis ​​(incluindo tipos de referência) como uma mônada, para que você não precise de um "operador de navegação segura".
precisa
3
O inventor das referências nulas chamou seu "erro de bilhão de dólares" e eu tendem a concordar. Veja infoq.com/presentations/…
Jamie Ide
Seu erro real é a mistura insegura (não imposta pelo idioma) de tipos anuláveis ​​e não anuláveis.
MSalters
@ JamieIde Obrigado por um link muito interessante. :)
BobRodes 01/01

Respostas:

182

A respeito

string y = (Session["key"] ?? "none").ToString();
Urso preto
fonte
79
A força é forte com este.
Chev
2
@ Matthew: Não, porque os valores da sessão são do tipo Objeto
BlackBear
1
@BlackBear mas o valor retornado é provavelmente uma corda, para que o elenco é válido
Firo
Esta foi a resposta mais direta à minha pergunta, por isso estou marcando a resposta, mas o método de extensão de Jon Skeet .ToStringOrDefault()é a minha maneira preferida de fazê-lo. No entanto, estou usando esta resposta dentro de método de extensão de Jon;)
Chev
10
Não gosto disso porque, se você tiver qualquer outro tipo de objeto recheado na sessão do que o esperado, poderá estar ocultando alguns erros sutis no seu programa. Prefiro usar uma transmissão segura, porque acho que é provável que ocorra erros mais rapidamente. Também evita chamar ToString () em um objeto de string.
tvanfosson
130

Se você costuma fazer isso especificamente comToString() isso, pode escrever um método de extensão:

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

...

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

Ou um método adotando um padrão, é claro:

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

...

string y = Session["key"].ToStringOrDefault("none");
Jon Skeet
fonte
16
.ToStringOrDefault()é simples e elegente. Uma boa solução.
Chev
7
Não posso concordar com isso. Os métodos de extensão on objectsão uma maldição e junk up uma base de código, e os métodos de extensão que operam sem erros em thisvalores nulos são pura maldade.
Nick Larsen
10
@ NickLarsen: Tudo com moderação, eu digo. Os métodos de extensão que funcionam com nulo podem ser muito úteis, IMO - desde que sejam claros sobre o que fazem.
precisa
3
@ one.beat.consumer: Sim. Se tivesse sido apenas a formatação (ou quaisquer erros de digitação), isso teria sido uma coisa, mas alterar o nome do método escolhido pelo autor está além da edição normalmente apropriada, IMO.
precisa
6
@ one.beat.consumer: Ao corrigir gramática e erros de digitação, tudo bem - mas mudar um nome que alguém (alguém, não apenas eu) claramente escolheu deliberadamente se sente diferente de mim. Nesse ponto, eu sugeriria isso em um comentário.
22612 Jon Skeet
21

Você também pode usar as, que gera nullse a conversão falhar:

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

Este voltaria "none"mesmo se alguém recheado um intno Session["key"].

Andomar
fonte
1
Isso só funciona quando você não precisaria ToString()em primeiro lugar.
Abel
1
Estou surpreso que ninguém tenha votado contra esta resposta ainda. Isso é semanticamente completamente diferente do que o OP quer fazer.
22412 Timwi
@ Timwi: O OP usa ToString()para converter um objeto que contém uma string em uma string. Você pode fazer o mesmo com obj as stringou (string)obj. É uma situação bastante comum no ASP.NET.
Andomar 22/03/12
5
@Andomar: Não, o OP está chamando ToString()um objeto (a saber, Session["key"]) cujo tipo ele não mencionou. Pode ser qualquer tipo de objeto, não necessariamente uma string.
22412 Timwi
13

Se sempre for um string, você pode transmitir:

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

Isso tem a vantagem de reclamar, em vez de esconder o erro, se alguém colocar intalgo ou algo Session["key"]. ;)

Ry-
fonte
10

Todas as soluções sugeridas são boas e respondem à pergunta; então isso é apenas para estender um pouco. Atualmente, a maioria das respostas lida apenas com validação nula e tipos de sequência. Você pode estender o StateBagobjeto para incluir um GetValueOrDefaultmétodo genérico , semelhante à resposta postada por Jon Skeet.

Um método simples de extensão genérica que aceita uma cadeia como chave e, em seguida, o tipo verifica o objeto da sessão. Se o objeto for nulo ou não for o mesmo tipo, o padrão será retornado, caso contrário, o valor da sessão será retornado com forte digitação.

Algo assim

/// <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
fonte
1
Você pode incluir uma amostra de uso para este método de extensão? StateBag não lida com o estado de exibição e não com a sessão? Estou usando o ASP.NET MVC 3, então não tenho realmente acesso simples para visualizar o estado. Eu acho que você quer estender HttpSessionState.
Chev
esta resposta requer recuperar o valor 3x e 2 lançamentos, se for bem-sucedido. (eu sei que é um dicionário, mas os novatos podem usar práticas semelhantes em métodos caros.)
Jake Berger
3
T value = source[key] as T; return value ?? defaultValue;
Jake Berger
1
@jberger A conversão para valor usando "as" é inacessível, pois não há uma restrição de classe no tipo genérico, pois você pode querer retornar um valor como bool. @AlexFord Minhas desculpas, você gostaria de estender a HttpSessionStatepara a sessão. :)
Richard
de fato. como observou Richard, requer a restrição. (... e um outro método, se você quiser usar tipos de valor)
Jake Berger
7

Nós usamos um método chamado 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();
    }
}
Timwi
fonte
Sim, esta é a resposta mais genérica para o problema pretendido - você me venceu - e um candidato à navegação segura (se você não se importa com os lambda-s por coisas simples) - mas ainda é um pouco complicado de escrever, bem :). Pessoalmente, eu sempre escolho o? : Em vez (se não é caro, se for, então reorganizar qualquer maneira) ...
NSGaga-principalmente-inativa
... E o 'Nomeação' é o verdadeiro problema com este - nada parece realmente descrever o que é correto (ou 'adiciona' muito) ou é longo - o NullOr é bom, mas com muita ênfase na IMO 'nula' (além de você have ?? still) - 'Propriedade' ou 'Seguro' é o que eu usei. value.Dot (o => o.property) ?? @default talvez?
NSGaga-inativo 26/03
@NSGaga: Nós conversamos sobre o nome por um bom tempo. Nós consideramosDot mas achamos isso muito descritivo. Decidimos NullOrcomo uma boa troca entre autoexplicação e brevidade. Se você realmente não se importava com o nome, sempre poderia chamá-lo _. Se você achar as lambdas muito pesadas para escrever, poderá usar um trecho para isso, mas pessoalmente acho bastante fácil. Quanto a ? :, você não pode usá-lo com expressões mais complexas, teria que movê-las para um novo local; NullOrpermite evitar isso.
Timwi
6

Minha preferência, por um lado, seria usar uma conversão segura para string, caso o objeto armazenado com a chave não seja um. O uso ToString()pode não ter os resultados desejados.

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

Como o @Jon Skeet diz, se você se encontra fazendo muito isso, é um método de extensão ou, melhor ainda, talvez um método de extensão em conjunto com uma classe SessionWrapper fortemente tipada. Mesmo sem o método de extensão, o wrapper fortemente tipado pode ser uma boa ideia.

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

Usado como

 var session = new SessionWrapper(Session);

 string key = session.Key;
 int maxAllowed = session.maxAllowed;
tvanfosson
fonte
3

criar uma função auxiliar

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


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

A resposta de Skeet é a melhor - particularmente acho que ToStringOrNull() é bastante elegante e atende melhor às suas necessidades. Eu queria adicionar mais uma opção à lista de métodos de extensão:

Retorne o objeto original ou o valor padrão da cadeia para 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");

Use varpara o valor retornado, pois ele retornará como o tipo de entrada original, apenas como a sequência padrão quandonull

one.beat.consumer
fonte
Por que lançar uma exceção null defaultValuese não for necessária (ou seja input != null)?
Attila
Uma avaliação input != nullretornará o objeto como ele próprio. input == nullretorna uma string fornecida como um parâmetro. portanto, é uma possível pessoa poderia chamar .OnNullAsString(null)- mas o propósito (embora método de extensão raramente útil) foi para garantir que você quer obter o objeto de volta ou string padrão ... Nunca nula
one.beat.consumer
O input!=nullcenário retornará a entrada apenas se defaultValue!=nulltambém reter; caso contrário, lançará um ArgumentNullException.
Átila
0

Este é o meu pequeno tipo "operador Elvis" seguro para versões do .NET que não suportam?

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

O primeiro argumento é o objeto testado. O segundo é a função. E terceiro é o valor nulo. Então, para o seu caso:

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

Também é muito útil para tipos anuláveis. Por exemplo:

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