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?


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

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

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"].


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"]. ;)


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

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

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;

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

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


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);
....
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.