Comparación nula o por defecto del argumento genérico en C #

Tengo un método genérico definido así:

public void MyMethod(T myArgument) 

Lo primero que quiero hacer es verificar si el valor de myArgument es el valor predeterminado para ese tipo, algo como esto:

 if (myArgument == default(T)) 

Pero esto no comstack porque no he garantizado que T implementará el operador ==. Así que cambié el código a esto:

 if (myArgument.Equals(default(T))) 

Ahora esto comstack, pero fallará si myArgument es nulo, que es parte de lo que estoy probando. Puedo agregar un cheque nulo explícito como este:

 if (myArgument == null || myArgument.Equals(default(T))) 

Ahora esto me parece redundante. ReSharper incluso está sugiriendo que cambie la parte myArgument == null en myArgument == default (T), que es donde comencé. ¿Hay una mejor manera de resolver este problema?

Necesito admitir tanto tipos de referencias como tipos de valores.

Para evitar el boxeo, la mejor forma de comparar generics para la igualdad es con EqualityComparer.Default . Esto respeta IEquatable (sin boxeo) así como object.Equals , y maneja todos los Nullable “levantados”. Por lo tanto:

 if(EqualityComparer.Default.Equals(obj, default(T))) { return obj; } 

Esto coincidirá:

  • null para las clases
  • nulo (vacío) para Nullable
  • cero / falso / etc. para otras estructuras

Qué tal esto:

 if (object.Equals(myArgument, default(T))) { //... } 

El uso del static object.Equals() evita la necesidad de que realice la comprobación null usted mismo. Calificando explícitamente la llamada con el object. probablemente no sea necesario según su contexto, pero normalmente prefijo las llamadas static con el nombre del tipo solo para que el código sea más soluble.

Pude localizar un artículo de Microsoft Connect que trata este tema con cierto detalle:

Desafortunadamente, este comportamiento es por diseño y no hay una solución fácil de usar con parámetros de tipo que puedan contener tipos de valores.

Si se sabe que los tipos son tipos de referencia, la sobrecarga predeterminada del objeto definido prueba las variables para la igualdad de referencia, aunque un tipo puede especificar su propia sobrecarga personalizada. El comstackdor determina qué sobrecarga usar en función del tipo estático de la variable (la determinación no es polimórfica). Por lo tanto, si cambia su ejemplo para restringir el parámetro de tipo genérico T a un tipo de referencia no sellada (como Excepción), el comstackdor puede determinar la sobrecarga específica que se utilizará y se comstackrá el siguiente código:

 public class Test where T : Exception 

Si se sabe que los tipos son tipos de valor, realiza pruebas de igualdad de valores específicas basadas en los tipos exactos utilizados. No existe una buena comparación “predeterminada” aquí dado que las comparaciones de referencias no son significativas en los tipos de valores y el comstackdor no puede saber qué valor de comparación específico debe emitir. El comstackdor podría emitir una llamada a ValueType.Equals (Object) pero este método utiliza la reflexión y es bastante ineficiente en comparación con las comparaciones de valores específicos. Por lo tanto, incluso si especificara una restricción de tipo de valor en T, no hay nada razonable para que el comstackdor genere aquí:

 public class Test where T : struct 

En el caso que usted presentó, donde el comstackdor ni siquiera sabe si T es un valor o un tipo de referencia, tampoco hay nada que generar que sea válido para todos los tipos posibles. Una comparación de referencia no sería válida para los tipos de valores y algún tipo de comparación de valores sería inesperado para los tipos de referencia que no se sobrecargan.

Esto es lo que puedes hacer …

He validado que ambos métodos funcionan para una comparación genérica de los tipos de referencia y valor:

 object.Equals(param, default(T)) 

o

 EqualityComparer.Default.Equals(param, default(T)) 

Para hacer comparaciones con el operador “==” necesitarás usar uno de estos métodos:

Si todos los casos de T derivan de una clase base conocida, puede informar al comstackdor utilizando restricciones de tipo genérico.

 public void MyMethod(T myArgument) where T : MyBase 

El comstackdor luego reconoce cómo realizar operaciones en MyBase y no arrojará el “Operador” == “no se puede aplicar a los operandos de tipo ‘T’ y ‘T'” error que está viendo ahora.

Otra opción sería restringir T a cualquier tipo que implemente IComparable .

 public void MyMethod(T myArgument) where T : IComparable 

Y luego use el método CompareTo definido por la interfaz IComparable .

Prueba esto:

 if (EqualityComparer.Default.Equals(myArgument, default(T))) 

eso debería comstackr y hacer lo que quieras.

(Editado)

Marc Gravell tiene la mejor respuesta, pero quería publicar un fragmento de código simple que trabajé para demostrarlo. Simplemente ejecute esto en una simple aplicación de consola C #:

 public static class TypeHelper { public static bool IsDefault(T val) { return EqualityComparer.Default.Equals(obj,default(T)); } } static void Main(string[] args) { // value type Console.WriteLine(TypeHelper.IsDefault(1)); //False Console.WriteLine(TypeHelper.IsDefault(0)); // True // reference type Console.WriteLine(TypeHelper.IsDefault("test")); //False Console.WriteLine(TypeHelper.IsDefault(null)); //True //True Console.ReadKey(); } 

Una cosa más: ¿alguien con VS2008 puede probar esto como un método de extensión? Estoy atascado con 2005 aquí y tengo curiosidad por ver si eso estaría permitido.


Editar: Aquí se muestra cómo hacer que funcione como un método de extensión:

 using System; using System.Collections.Generic; class Program { static void Main() { // value type Console.WriteLine(1.IsDefault()); Console.WriteLine(0.IsDefault()); // reference type Console.WriteLine("test".IsDefault()); // null must be cast to a type Console.WriteLine(((String)null).IsDefault()); } } // The type cannot be generic public static class TypeHelper { // I made the method generic instead public static bool IsDefault(this T val) { return EqualityComparer.Default.Equals(val, default(T)); } } 

Para manejar todos los tipos de T, incluyendo donde T es un tipo primitivo, deberá comstackr en ambos métodos de comparación:

  T Get(Func createObject) { T obj = createObject(); if (obj == null || obj.Equals(default(T))) return obj; // .. do a bunch of stuff return obj; } 

Va a haber un problema aquí –

Si va a permitir que esto funcione para cualquier tipo, el valor predeterminado (T) siempre será nulo para los tipos de referencia, y 0 (o estructura llena de 0) para los tipos de valor.

Sin embargo, probablemente este no sea el comportamiento que buscas. Si desea que esto funcione de una manera genérica, probablemente necesite usar la reflexión para verificar el tipo de T y manejar tipos de valores diferentes a los tipos de referencia.

Alternativamente, podría poner una restricción de interfaz sobre esto, y la interfaz podría proporcionar una forma de verificar el valor predeterminado de la clase / estructura.

Creo que probablemente necesites dividir esta lógica en dos partes y comprobar primero si es nula.

 public static bool IsNullOrEmpty(T value) { if (IsNull(value)) { return true; } if (value is string) { return string.IsNullOrEmpty(value as string); } return value.Equals(default(T)); } public static bool IsNull(T value) { if (value is ValueType) { return false; } return null == (object)value; } 

En el método IsNull, confiamos en el hecho de que los objetos ValueType no pueden ser nulos por definición, de modo que si el valor pasa a ser una clase derivada de ValueType, ya sabemos que no es nulo. Por otro lado, si no es un tipo de valor, entonces podemos simplemente comparar el lanzamiento de valor a un objeto contra nulo. Podríamos evitar el control contra ValueType yendo directamente a un elenco para objetar, pero eso significaría que un tipo de valor se pondría en un recuadro, que es algo que probablemente queremos evitar ya que implica que se crea un nuevo objeto en el montón.

En el método IsNullOrEmpty, estamos verificando el caso especial de una cadena. Para todos los demás tipos, estamos comparando el valor (que ya sabemos no es nulo) con su valor predeterminado, que para todos los tipos de referencia es nulo y para los tipos de valor suele ser alguna forma de cero (si son integrales).

Usando estos métodos, el siguiente código se comporta como es de esperar:

 class Program { public class MyClass { public string MyString { get; set; } } static void Main() { int i1 = 1; Test("i1", i1); // False int i2 = 0; Test("i2", i2); // True int? i3 = 2; Test("i3", i3); // False int? i4 = null; Test("i4", i4); // True Console.WriteLine(); string s1 = "hello"; Test("s1", s1); // False string s2 = null; Test("s2", s2); // True string s3 = string.Empty; Test("s3", s3); // True string s4 = ""; Test("s4", s4); // True Console.WriteLine(); MyClass mc1 = new MyClass(); Test("mc1", mc1); // False MyClass mc2 = null; Test("mc2", mc2); // True } public static void Test(string fieldName, T field) { Console.WriteLine(fieldName + ": " + IsNullOrEmpty(field)); } // public static bool IsNullOrEmpty(T value) ... // public static bool IsNull(T value) ... } 

Yo suelo:

 public class MyClass { private bool IsNull() { var nullable = Nullable.GetUnderlyingType(typeof(T)) != null; return nullable ? EqualityComparer.Default.Equals(Value, default(T)) : false; } } 

No sé si esto funciona con sus requisitos o no, pero podría restringir T a ser un Tipo que implemente una interfaz como IComparable y luego usar el método ComparesTo () desde esa interfaz (que IIRC soporta / maneja nulos) como este :

 public void MyMethod(T myArgument) where T : IComparable ... if (0 == myArgument.ComparesTo(default(T))) 

Probablemente haya otras interfaces que podría usar, así como IEquitable, etc.

@ilitirit:

 public class Class where T : IComparable { public T Value { get; set; } public void MyMethod(T val) { if (Value == val) return; } } 

El operador ‘==’ no se puede aplicar a operandos del tipo ‘T’ y ‘T’

No puedo pensar en una forma de hacer esto sin la prueba nula explícita seguida de invocar el método Equals o el objeto. Iguales como se sugirió anteriormente.

Puede diseñar una solución utilizando System.Comparison, pero realmente eso terminará con más líneas de código y boostá sustancialmente la complejidad.

Creo que estabas cerca.

 if (myArgument.Equals(default(T))) 

Ahora esto comstack, pero fallará si myArgument es nulo, que es parte de lo que estoy probando. Puedo agregar un cheque nulo explícito como este:

Solo necesita invertir el objeto sobre el que se llama a los iguales para un enfoque elegante nulo seguro.

 default(T).Equals(myArgument);