¿Cuál es la forma correcta de verificar los valores nulos?

122

Me encanta el operador de fusión nula porque facilita la asignación de un valor predeterminado para los tipos anulables.

 int y = x ?? -1;

Eso es genial, excepto si necesito hacer algo simple con x. Por ejemplo, si quiero verificar Session, entonces generalmente termino teniendo que escribir algo más detallado.

Desearía poder hacer esto:

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

Pero no puede porque .ToString()se llama antes de la verificación nula, por lo que falla si Session["key"]es nulo. Termino haciendo esto:

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

Funciona y es mejor, en mi opinión, que la alternativa de tres líneas:

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

Aunque eso funciona, todavía tengo curiosidad por saber si hay una mejor manera. Parece que no importa lo que siempre tengo que hacer referencia Session["key"]dos veces; una vez para el cheque y otra vez para la tarea. ¿Algunas ideas?

Chev
fuente
20
Esto es cuando me gustaría que C # tuviera un "operador de navegación seguro" ( .?) como Groovy .
Cameron
2
@Cameron: Esto es cuando desearía que C # pudiera tratar los tipos anulables (incluidos los tipos de referencia) como una mónada, por lo que no necesitaría un "operador de navegación seguro".
Jon Purdy
3
El inventor de referencias nulas lo llamó su "error de mil millones de dólares" y tiendo a estar de acuerdo. Ver infoq.com/presentations/…
Jamie Ide
Su error real es la mezcla insegura (no aplicada por el lenguaje) de tipos anulables y no nullabel.
MSalters
@JamieIde Gracias por un enlace muy interesante. :)
BobRodes 01 de

Respuestas:

182

Qué pasa

string y = (Session["key"] ?? "none").ToString();
Oso negro
fuente
79
La fuerza es fuerte con éste.
Chev
2
@Matthew: No porque los valores de la sesión son de tipo Object
BlackBear
1
@BlackBear pero el valor devuelto probablemente sea una cadena, por lo que el reparto es válido
Firo
Esta fue la respuesta más directa a mi pregunta, así que estoy marcando la respuesta, pero el método de extensión de Jon Skeet .ToStringOrDefault()es mi forma preferida de hacerlo. Sin embargo, estoy usando esta respuesta dentro del método de extensión de Jon;)
Chev
10
No me gusta esto porque si tiene cualquier otro tipo de objeto relleno en la sesión que no sea lo que espera, puede estar ocultando algunos errores sutiles en su programa. Prefiero usar un molde seguro porque creo que es probable que surjan errores más rápido. También evita llamar a ToString () en un objeto de cadena.
tvanfosson
130

Si con frecuencia haces esto específicamente con,ToString() entonces podrías escribir un método de extensión:

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

...

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

O un método que toma un valor predeterminado, por supuesto:

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

...

string y = Session["key"].ToStringOrDefault("none");
Jon Skeet
fuente
16
.ToStringOrDefault()Es simple y elegante. Una buena solución
Chev
77
No puedo estar de acuerdo con esto en absoluto. Los métodos de extensión objectson una maldición y desechan una base de código, y los métodos de extensión que operan sin error en thisvalores nulos son pura maldad.
Nick Larsen
10
@NickLarsen: Todo con moderación, digo. Los métodos de extensión que funcionan con nulo pueden ser muy útiles, en mi opinión, siempre que tengan claro lo que hacen.
Jon Skeet
3
@ one.beat.consumer: Sí. Si hubiera simplemente sido el formateo (o ninguna errata), que habría sido una cosa, pero el cambio de elección de un autor del nombre del método está más allá de lo que es la edición normalmente corresponde, la OMI.
Jon Skeet
66
@ one.beat.consumer: Al corregir la gramática y los errores tipográficos, está bien, pero cambiar un nombre que alguien (cualquiera, no solo yo) ha elegido claramente deliberadamente se siente diferente a mí. En ese punto, lo sugeriría en un comentario.
Jon Skeet
21

También puede usar as, que produce nullsi la conversión falla:

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

Esto volvería "none"incluso si alguien metió una inten Session["key"].

Andomar
fuente
1
Esto solo funciona cuando no lo necesitarías ToString()en primer lugar.
Abel
1
Me sorprende que nadie haya rechazado esta respuesta todavía. Esto es semánticamente completamente diferente de lo que el OP quiere hacer.
Timwi
@Timwi: El OP utiliza ToString()para convertir un objeto que contiene una cadena en una cadena. Puedes hacer lo mismo con obj as stringo (string)obj. Es una situación bastante común en ASP.NET.
Andomar
55
@Andomar: No, el OP está llamando ToString()a un objeto (es decir,Session["key"] ) cuyo tipo no mencionó. Podría ser cualquier tipo de objeto, no necesariamente una cadena.
Timwi
13

Si siempre será un string, puedes lanzar:

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

Esto tiene la ventaja de quejarse en lugar de ocultar el error si alguien mete intalgo o algo Session["key"]. ;)

Ry-
fuente
10

Todas las soluciones sugeridas son buenas y responden la pregunta; así que esto es solo para extenderlo un poco. Actualmente, la mayoría de las respuestas solo tratan con validación nula y tipos de cadenas. Puede extender el StateBagobjeto para incluir un genéricoGetValueOrDefault método , similar a la respuesta publicada por Jon Skeet.

Un método de extensión genérico simple que acepta una cadena como clave y luego escribe verifica el objeto de sesión. Si el objeto es nulo o no es del mismo tipo, se devuelve el valor predeterminado; de lo contrario, el valor de la sesión se devuelve fuertemente tipado.

Algo como esto

/// <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;
}
Ricardo
fuente
1
¿Puede incluir una muestra de uso para este método de extensión? ¿StateBag no trata con el estado de vista y no con la sesión? Estoy usando ASP.NET MVC 3, así que realmente no tengo acceso simple para ver el estado. Creo que quieres extender HttpSessionState.
Chev
Esta respuesta requiere recuperar el valor 3x y 2 lanzamientos si tiene éxito. (Sé que es un diccionario, pero los principiantes pueden usar prácticas similares en métodos caros.)
Jake Berger
3
T value = source[key] as T; return value ?? defaultValue;
Jake Berger
1
@jberger Transmitir al valor usando "as" es inaccesible ya que no hay una restricción de clase en el tipo genérico, ya que posiblemente desee devolver un valor como bool. @AlexFord Mis disculpas, querrás extender HttpSessionStatela sesión. :)
Richard
en efecto. Como Richard señaló, requiere la restricción. (... y otro método si desea utilizar tipos de valor)
Jake Berger
7

Usamos un método llamado 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());

Fuente

/// <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
fuente
Sí, esta es la respuesta más genérica al problema que se pretende: me ganas, y un candidato para una navegación segura (si no te importan los lambda-s para cosas simples), pero aún es un poco engorroso escribir, bien :). Personalmente siempre elegiría el? : en cambio (si no es costoso, si se reorganiza de todos modos) ...
NSGaga-mostly-inactive
... Y el 'Naming' es el verdadero problema con este: nada realmente parece representar correctamente (o 'agrega' demasiado), o es largo: NullOr es bueno, pero hace demasiado hincapié en la IMO 'nula' (más usted have ?? still) - 'Propiedad', o 'Seguro' es lo que usé. value.Dot (o => o.property) ?? @ por defecto tal vez?
NSGaga-mayoría-inactivo
@NSGaga: Estuvimos yendo y viniendo sobre el nombre durante bastante tiempo. Lo consideramos Dotpero lo encontramos muy poco descriptivo. Nos conformamos con NullOruna buena compensación entre la autoexplicación y la brevedad. Si realmente no le importaba el nombre, siempre podría llamarlo _. Si encuentra que las lambdas son demasiado engorrosas para escribir, puede usar un fragmento para esto, pero personalmente me parece bastante fácil. En cuanto a ? :, no puede usar eso con expresiones más complejas, tendría que moverlas a un nuevo local; NullOrte permite evitar eso.
Timwi
6

Mi preferencia, para una sola vez, sería usar una conversión segura a cadena en caso de que el objeto almacenado con la clave no sea uno. Usar ToString()puede no tener los resultados que desea.

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

Como dice @Jon Skeet, si te encuentras haciendo esto mucho como un método de extensión o, mejor aún, tal vez un método de extensión junto con una clase SessionWrapper fuertemente tipada. Incluso sin el método de extensión, el contenedor fuertemente tipado podría ser una buena 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 }
    }
}

Usado como

 var session = new SessionWrapper(Session);

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

crear una función 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
fuente
2

La respuesta de Skeet es la mejor, en particular creo que la suya ToStringOrNull()es bastante elegante y se adapta mejor a tus necesidades. Quería agregar una opción más a la lista de métodos de extensión:

Devuelve el objeto original o el valor de cadena predeterminado para nulo :

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

Úselo varpara el valor devuelto, ya que volverá como el tipo de entrada original, solo como la cadena predeterminada cuandonull

one.beat.consumer
fuente
¿Por qué lanzar una excepción null defaultValuesi no es necesaria (es decir input != null)?
Atila
Una input != nullevaluación devolverá el objeto como sí mismo. input == nulldevuelve una cadena proporcionada como parámetro. por lo tanto, es posible que una persona pueda llamar .OnNullAsString(null), pero el propósito (aunque rara vez es un método de extensión útil) era asegurarse de recuperar el objeto o la cadena predeterminada ... nunca nulo
one.beat.consumer
El input!=nullescenario solo devolverá la entrada si defaultValue!=nulltambién se cumple; de lo contrario arrojará un ArgumentNullException.
Attila
0

Este es mi pequeño tipo de "operador de Elvis" seguro para versiones de .NET que no son compatibles.

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

El primer argumento es el objeto probado. El segundo es la función. Y tercero es el valor nulo. Entonces para su caso:

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

También es muy útil para los tipos anulables. Por ejemplo:

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