2009-02-19 10 views
22

Necesito comprobar un objeto genérico para nulo, o predeterminado (T). Pero tengo un problema ... Actualmente lo he hecho así:C#: alternativa a GenericType == null

if (typeof(T).IsValueType) 
{ 
    if(default(T).Equals(thing)) 
    // Do something 
    else 
    // Do something else 
} 
else 
{ 
    if(thing == null) 
    // Do something 
    else 
    // Do something else 
} 

Pero entonces me acaban de repetir lo mismo ... lo que no me gusta. El problema es el siguiente:

thing == null; 

Aquí ReSharper advierte sobre la posible comparación del tipo de valor con 'nulo'.

thing == default(T); 

Aquí me sale error de compilación: No se puede aplicar el operador '==' a operandos de tipo 'T' y 'T'.

thing.Equals(null|default(T)); 

thing, obviamente, puede ser nulo (es por eso que tengo que comprobar!), Por lo que hará que NullReferenceException.

null|default(T).Equals(thing); 

nulo y predeterminado (T) es muy a menudo nulo, así ...

¿Hay una manera limpia de hacer esto ??

Respuesta

26

Si el boxeo no es un problema, es posible que utilices:

object.Equals(value, default(T)) 
+0

No sabía que eso fuera posible. – configurator

+0

y eso funcionará si ambos son nulos? – Svish

+3

Lo hace. Revisar no es tan difícil, Svish ... – configurator

2

Lo mejor que se me ocurre en este momento es:

return value == null || value.Equals(default(T)); 

Editar:

Al parecer, hay una object.Equals método estático que no estaba al tanto de:

return object.Equals(value, default(T)); 

Este es mejor.

+0

pero ¿qué sucederá si el valor es un tipo de valor? – Svish

+0

(value == null) devolvería false. (value.Equals (predeterminado (T)) se compararía con el valor predeterminado – configurator

+0

Esto solo evita el boxeo si hay una implementación Equals en el tipo que es especializado en lugar de la predeterminada que toma un objeto. A la luz de eso, creo que object.Equals (value, default (T)) es más fácil de leer. –

2

Un poco de boxeo hará bien el trabajo.

static bool IsNullOrDefault<T>(T value) 
    { 
     return ((object)default(T)) == null ? 
      ((object)value) == null : 
      default(T).Equals(value); 
    } 
+0

¿No es esto solo decir objeto? .Equals (valor predeterminado (T), valor)? –

0

con las pruebas:

public class DefaultOrNullChecker<T> { 
    public bool Check(object x) { 
     return object.ReferenceEquals(x, null) || x.Equals(default(T)); 
    } 
} 
[TestFixture] 
public class Tests { 
    [Test] public void when_T_is_reference_type() { 
     Assert.IsFalse(new DefaultOrNullChecker<Exception>().Check(new Exception()));} 
    [Test] public void when_T_is_value_type() { 
     Assert.IsFalse(new DefaultOrNullChecker<int>().Check(123)); } 
    [Test] public void when_T_is_null() { 
     Assert.IsTrue(new DefaultOrNullChecker<Exception>().Check(null));} 
    [Test] public void when_T_is_default_value() { 
     Assert.IsTrue(new DefaultOrNullChecker<int>().Check(0)); } 
} 
+0

Esto no importa en absoluto, ya que la comparación se realiza en la restricción de T frente a la restricción de T, es decir, object == object. Es lo mismo que object.Re FerenceEquals. – configurator

1

usted puede evitar por completo el boxeo señalando que nulabilidad de un tipo puede ser determinado estáticamente

Esto es lo que puede hacer:

  1. declarar una estática privada de sólo lectura variable llamada isDefault de tipo Predicate<T> en su clase genérica
  2. Añadir un inicializador estático a su clase genérica, donde se verifica T nulabilidad, y establecer isDefault a cualquiera v==null o default(T).Equals(v) dependiendo del resultado
  3. uso isDefault(x) en lugar de x==null en el resto de su código

Aquí se muestra un ejemplo:

public class Example<T> { 

    private static readonly Predicate<T> isDefault; 

    static Example() { 
     // Nullability check is a bit ugly, but we do it once per T, 
     // so what the heck... 
     if (typeof(T).IsValueType && 
      (!typeof(T).IsGenericType 
     || typeof(T).GetGenericTypeDefinition() != typeof(Nullable<>) 
     )) { 
      // This is safe because T is not null 
      isDefault = val => default(T).Equals(val); 
     } else { 
      // T is not a value type, so its default is null 
      isDefault = val => val == null; 
     } 
    } 

    public bool Check(T value) { 
     // Now our null-checking is both good-looking and efficient 
     return isDefault(value); 
    } 

} 
+0

¿Qué sucede si el método es genérico pero la clase no? –

53

La forma correcta de hacer esto es:

return EqualityComparer<T>.Default.Equals(value, default(T)) 

Sin boxeo. Incluso se puede definir un método de extensión de esta manera:

public static void bool IsDefault<T>(this T value) 
{ 
    return EqualityComparer<T>.Default.Equals(value, default(T)); 
} 

.. e invocar así:

return entry.IsDefault(); 

Aunque, personalmente no me importa para los métodos de extensión en T (por ejemplo, este objeto EsNulo()) ya que dificulta la legibilidad a veces.

+0

Oh, bueno :) – Svish

+0

+1 para no boxear. – Olhovsky

+1

Este es un código pequeño y brillante de código. Gracias por compartir. – kevinarpe

-1

¿Qué pasa con esto?

if (thing == default(T)) { } 

Si se trata de un tipo de valor, el JIT simplemente eliminará la declaración por completo.

+0

No compila. 'No se puede aplicar el operador '==' a los operandos de tipo 'T' y 'T'' –

7

Cuando necesito comprobar si el valor es NULL, utilizo el siguiente método. Normalmente uso esto cuando llamo a métodos que toman cualquier tipo pero no nulos, como el Caché.

public static bool IsNull<T>(this T value) 
{ 
    var type = typeof(T); 

    return (type.IsClass || Nullable.GetUnderlyingType(type) != null) 
     && EqualityComparer<T>.Default.Equals(value, default(T)); 

} 
+0

' Type.IsClass' es falso para las interfaces. '! Type.IsValueType' es probablemente lo que quieres. (Creo que todavía necesita el control que admite nulos). – hangar