referencia por pase pasar pasaje parametros objetos funciones como argumentos c# generics

pase - Comparación nula o por defecto de un argumento genérico en C#



pasar objetos como parametros en c# (12)

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

public void MyMethod<T>(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 se compila porque no he garantizado que T implementará el operador ==. Así que cambié el código a esto:

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 un cheque nulo explícito como este:

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

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

Necesito soportar tanto tipos de referencias como tipos de valores.


(Editado)

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

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: ¿puede alguien con VS2008 probar esto como un método de extensión? Estoy atascado con el 2005 aquí y tengo curiosidad por ver si eso sería permitido.

Edición: aquí es 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<T>(this T val) { return EqualityComparer<T>.Default.Equals(val, default(T)); } }


@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 los 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 o el objeto de Equals.

Puedes diseñar una solución usando System.Comparison, pero en realidad eso va a terminar con muchas más líneas de código y aumentará la complejidad sustancialmente.


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 un cheque nulo explícito como este:

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

default(T).Equals(myArgument);


Creo que probablemente deba dividir esta lógica en dos partes y comprobar primero si hay nulos.

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, confiamos en el hecho de que los objetos ValueType no pueden ser nulos por definición, por lo que si el valor es una clase que se deriva de ValueType, ya sabemos que no es nulo. Por otro lado, si no es un tipo de valor, podemos comparar el valor de conversión de un objeto con un valor nulo. Podríamos evitar la verificación contra ValueType yendo directamente a una conversión a objeto, pero eso significaría que un tipo de valor quedaría enmarcado, algo que probablemente queremos evitar ya que implica que se crea un nuevo objeto en el montón.

En el método IsNullOrEmpty, estamos comprobando el caso especial de una cadena. Para todos los demás tipos, comparamos el valor (que ya sabemos que no es nulo) con su valor predeterminado, que para todos los tipos de referencia es nulo y para los tipos de valor generalmente es una 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<T>(string fieldName, T field) { Console.WriteLine(fieldName + ": " + IsNullOrEmpty(field)); } // public static bool IsNullOrEmpty<T>(T value) ... // public static bool IsNull<T>(T value) ... }


No sé si esto funciona con sus requisitos o no, pero podría restringir que T sea un Tipo que implementa una interfaz como IComparable y luego usar el método ComparesTo () de 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 hay otras interfaces que podrías usar también como IEquitable, etc.


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

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

Esto coincidirá con:

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

Para manejar todos los tipos de T, incluso cuando T es un tipo primitivo, deberá compilar 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; }


Prueba esto:

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

Eso debería compilar, y hacer lo que quieras.


Pude localizar un artículo de Microsoft Connect que trata este problema en detalle:

Desafortunadamente, este comportamiento es por diseño y no hay una solución fácil para habilitar el uso de parámetros de tipo que puedan contener tipos de valor.

Si se sabe que los tipos son tipos de referencia, la sobrecarga predeterminada de las variables de pruebas de objeto definidas para la igualdad de referencia, aunque un tipo puede especificar su propia sobrecarga personalizada. El compilador 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 sellado (como Excepción), el compilador puede determinar la sobrecarga específica que se utilizará y el siguiente código se compilaría:

public class Test<T> where T : Exception

Si se sabe que los tipos son tipos de valor, realiza pruebas de igualdad de valor específicas basadas en los tipos exactos utilizados. No hay una buena comparación "por defecto" aquí ya que las comparaciones de referencia no son significativas en los tipos de valor y el compilador no puede saber qué comparación de valor específico emitir. El compilador podría emitir una llamada a ValueType.Equals (Objeto), 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 tuviera que especificar una restricción de tipo de valor en T, no hay nada razonable para 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 referencia no sería válida para los tipos de valor y algún tipo de comparación de valor sería inesperada 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 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á usar uno de estos métodos:

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

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

El compilador luego reconoce cómo realizar operaciones en MyBase y no lanzará el MyBase "Operator ''=='' no se puede aplicar a los 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 use el método CompareTo definido por la interfaz de IComparable .


Qué tal esto:

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

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


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 completa de 0) para los tipos de valor.

Sin embargo, probablemente este no sea el comportamiento que estás buscando. 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 en esto, y la interfaz podría proporcionar una manera de verificar el valor predeterminado de la clase / estructura.


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