Pregunta

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

public void MyMethod<T>(T myArgument)

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

if (myArgument == default(T))

Pero esto no se compila porque no he garantizado que T implementará el operador ==.Entonces cambié el código a este:

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

Ahora esto se compila, pero fallará si myArgument es nulo, que es parte de lo que estoy probando.Puedo agregar una verificación nula explícita como esta:

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

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

necesito apoyar ambos tipos de referencias y tipos de valores.

¿Fue útil?

Solución

Para evitar el boxeo, la mejor manera de comparar los genéricos en busca de igualdad es con EqualityComparer<T>.Default.Esto respeta IEquatable<T> (sin boxeo) así como object.Equals, y maneja todos los Nullable<T> matices "elevados".Por eso:

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

Esto coincidirá:

  • nulo para clases
  • nulo (vacío) para Nullable<T>
  • cero/falso/etc para otras estructuras

Otros consejos

Qué tal esto:

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

Utilizando el static object.Equals() método evita la necesidad de que usted haga el null compruébalo tú mismo.Calificando explícitamente la convocatoria con object. Probablemente no sea necesario dependiendo de su contexto, pero normalmente prefijo static llama con el nombre del tipo solo para hacer que el código sea más soluble.

Pude localizar un Artículo de Microsoft Connect que analiza este tema con cierto detalle:

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

Si se sabe que los tipos son tipos de referencia, la sobrecarga predeterminada de variables definidas en el objeto prueba la igualdad de referencia, aunque un tipo puede especificar su propia sobrecarga personalizada.El compilador determina qué sobrecarga usar según el 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 sellado (como Exception), el compilador puede determinar la sobrecarga específica a usar y se compilará el siguiente código:

public class Test<T> where T : Exception

Si se sabe que los tipos son tipos de valores, realiza pruebas de igualdad de valores específicas basadas en los tipos exactos utilizados.Aquí no existe una buena comparación "predeterminada" ya que las comparaciones de referencias no son significativas en los tipos de valores y el compilador no puede saber qué comparación de valores específicos emitir.El compilador 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 que el compilador genere aquí:

public class Test<T> where T : struct

En el caso que presentó, donde el compilador 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 referencias no sería válida para tipos de valores y algún tipo de comparación de valores sería inesperada para tipos de referencias que no se sobrecargan.

Esto es lo que puedes hacer...

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

object.Equals(param, default(T))

o

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

Para hacer comparaciones con el operador "==" necesitarás utilizar uno de estos métodos:

Si todos los casos de T derivan de una clase base conocida, puede informarle al compilador mediante restricciones de tipo genéricas.

public void MyMethod<T>(T myArgument) where T : MyBase

Luego, el compilador reconoce cómo realizar operaciones en MyBase y no arrojará el error "El operador '==' no se puede aplicar a operandos de tipo 'T' y 'T'" que está viendo ahora.

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

public void MyMethod<T>(T myArgument) where T : IComparable

Y luego usa el CompareTo método definido por el Interfaz comparable.

Prueba esto:

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

eso debería compilarse y hacer lo que quieras.

(Editado)

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

public static class TypeHelper<T>
{
    public static bool IsDefault(T val)
    {
         return EqualityComparer<T>.Default.Equals(obj,default(T));
    }
}

static void Main(string[] args)
{
    // value type
    Console.WriteLine(TypeHelper<int>.IsDefault(1)); //False
    Console.WriteLine(TypeHelper<int>.IsDefault(0)); // True

    // reference type
    Console.WriteLine(TypeHelper<string>.IsDefault("test")); //False
    Console.WriteLine(TypeHelper<string>.IsDefault(null)); //True //True

    Console.ReadKey();
}

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


Editar: A continuación se explica cómo hacerlo funcionar como 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<T>(this T val)
    {
        return EqualityComparer<T>.Default.Equals(val, default(T));
    }
}

Para manejar todos los tipos de T, incluso cuando T es un tipo primitivo, necesitarás compilar con ambos métodos de comparación:

    T Get<T>(Func<T> createObject)
    {
        T obj = createObject();
        if (obj == null || obj.Equals(default(T)))
            return obj;

        // .. do a bunch of stuff
        return obj;
    }

Aquí habrá un problema...

Si va a permitir que esto funcione para cualquier tipo, default(T) siempre será nulo para los tipos de referencia y 0 (o una 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 manera genérica, probablemente necesite usar la reflexión para verificar el tipo de T y manejar tipos de valor diferentes a los tipos de referencia.

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

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

public static bool IsNullOrEmpty<T>(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>(T value)
{
    if (value is ValueType)
    {
        return false;
    }
    return null == (object)value;
}

En el método IsNull, nos basamos en el hecho de que los objetos ValueType no pueden ser nulos por definición, por lo que si el valor resulta ser una clase que deriva de ValueType, ya sabemos que no es nulo.Por otro lado, si no es un tipo de valor, entonces podemos simplemente comparar el valor emitido a un objeto con nulo.Podríamos evitar la verificación de ValueType yendo directamente a una conversión a objeto, pero eso significaría que un tipo de valor quedaría encuadrado, lo cual es algo que probablemente queramos evitar, ya que implica que se crea un nuevo objeto en el montón.

En el método IsNullOrEmpty, buscamos el caso especial de una cadena.Para todos los demás tipos, estamos comparando el valor (que ya sabemos que es no null) contra 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).

Con 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<T>(string fieldName, T field)
    {
        Console.WriteLine(fieldName + ": " + IsNullOrEmpty(field));
    }

    // public static bool IsNullOrEmpty<T>(T value) ...

    // public static bool IsNull<T>(T value) ...
}

Yo suelo:

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

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

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

Probablemente haya otras interfaces que también podría utilizar, IEquitable, etc.

@ilitirit:

public class Class<T> 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 de tipo 'T' y 'T'

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

Puede idear una solución utilizando System.Comparison, pero en realidad eso terminará con muchas más líneas de código y aumentará sustancialmente la complejidad.

Creo que estabas cerca.

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

Ahora esto se compila, pero fallará si myArgument es nulo, que es parte de lo que estoy probando.Puedo agregar una verificación nula explícita como esta:

Sólo necesita invertir el objeto en el que se llama a los iguales para un enfoque elegante y seguro para nulos.

default(T).Equals(myArgument);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top