tipos tipo ser referencia genericos debe c# generics operators equals-operator

c# - genericos - el tipo t debe ser un tipo de referencia



¿No se puede aplicar el operador== a tipos genéricos en C#? (11)

"... por defecto == se comporta como se describe anteriormente para los tipos de referencia predefinidos y definidos por el usuario".

El tipo T no es necesariamente un tipo de referencia, por lo que el compilador no puede hacer esa suposición.

Sin embargo, esto se compilará porque es más explícito:

bool Compare<T>(T x, T y) where T : class { return x == y; }

Haga un seguimiento de la pregunta adicional: "Pero, en caso de que esté usando un tipo de referencia, ¿el operador == usaría la comparación de referencia predefinida, o usaría la versión sobrecargada del operador si un tipo definido?"

Pensé que == en los Genéricos usaría la versión sobrecargada, pero la siguiente prueba demuestra lo contrario. Interesante ... ¡Me encantaría saber por qué! Si alguien sabe por favor compartir.

namespace TestProject { class Program { static void Main(string[] args) { Test a = new Test(); Test b = new Test(); Console.WriteLine("Inline:"); bool x = a == b; Console.WriteLine("Generic:"); Compare<Test>(a, b); } static bool Compare<T>(T x, T y) where T : class { return x == y; } } class Test { public static bool operator ==(Test a, Test b) { Console.WriteLine("Overloaded == called"); return a.Equals(b); } public static bool operator !=(Test a, Test b) { Console.WriteLine("Overloaded != called"); return a.Equals(b); } } }

Salida

Inline: Sobrecargado == llamado

Genérico:

Pulse cualquier tecla para continuar . . .

Seguimiento 2

Quiero señalar que cambiar mi método de comparación a

static bool Compare<T>(T x, T y) where T : Test { return x == y; }

hace que se llame al operador sobrecargado ==. Supongo que sin especificar el tipo (como dónde ), el compilador no puede inferir que debería usar el operador sobrecargado ... aunque creo que tendría suficiente información para tomar esa decisión, incluso sin especificar el tipo.

De acuerdo con la documentación del operador == en MSDN ,

Para tipos de valores predefinidos, el operador de igualdad (==) devuelve verdadero si los valores de sus operandos son iguales, de lo contrario, falso. Para tipos de referencia distintos a la cadena, == devuelve verdadero si sus dos operandos se refieren al mismo objeto. Para el tipo de cadena, == compara los valores de las cadenas. Los tipos de valor definidos por el usuario pueden sobrecargar el operador == (ver operador). Lo mismo ocurre con los tipos de referencia definidos por el usuario, aunque por defecto == se comporta como se describe anteriormente para los tipos de referencia predefinidos y definidos por el usuario.

Entonces, ¿por qué este fragmento de código no se compila?

bool Compare<T>(T x, T y) { return x == y; }

Recibo el error El Operador ''=='' no se puede aplicar a los operandos de tipo ''T'' y ''T'' . Me pregunto por qué, ya que, según tengo entendido, el operador == está predefinido para todos los tipos.

Edit: gracias a todos. Al principio no me di cuenta de que la declaración era solo sobre tipos de referencia. También pensé que se proporciona una comparación bit a bit para todos los tipos de valores, que ahora sé que no es correcto.

Pero, en caso de que esté usando un tipo de referencia, ¿el operador == usaría la comparación de referencia predefinida, o usaría la versión sobrecargada del operador si un tipo definido?

Edición 2: A través de prueba y error, aprendimos que el operador == usará la comparación de referencia predefinida cuando use un tipo genérico sin restricciones. En realidad, el compilador utilizará el mejor método que pueda encontrar para el argumento de tipo restringido, pero no buscará más. Por ejemplo, el siguiente código siempre se imprimirá true , incluso cuando se Test.test<B>(new B(), new B()) :

class A { public static bool operator==(A x, A y) { return true; } } class B : A { public static bool operator==(B x, B y) { return false; } } class Test { void test<T>(T a, T b) where T : A { Console.WriteLine(a == b); } }


Bueno, en mi caso, quería hacer una prueba unitaria del operador de igualdad. Necesitaba llamar al código bajo los operadores de igualdad sin establecer explícitamente el tipo genérico. Los consejos para EqualityComparer no fueron útiles, ya que EqualityComparer llamó al método Equals pero no al operador de igualdad.

Aquí es cómo conseguí este trabajo con tipos genéricos construyendo un LINQ . Llama al código correcto para los operadores == y != :

/// <summary> /// Gets the result of "a == b" /// </summary> public bool GetEqualityOperatorResult<T>(T a, T b) { // declare the parameters var paramA = Expression.Parameter(typeof(T), nameof(a)); var paramB = Expression.Parameter(typeof(T), nameof(b)); // get equality expression for the parameters var body = Expression.Equal(paramA, paramB); // compile it var invokeEqualityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile(); // call it return invokeEqualityOperator(a, b); } /// <summary> /// Gets the result of "a =! b" /// </summary> public bool GetInequalityOperatorResult<T>(T a, T b) { // declare the parameters var paramA = Expression.Parameter(typeof(T), nameof(a)); var paramB = Expression.Parameter(typeof(T), nameof(b)); // get equality expression for the parameters var body = Expression.NotEqual(paramA, paramB); // compile it var invokeInequalityOperator = Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile(); // call it return invokeInequalityOperator(a, b); }


Como han dicho otros, solo funcionará cuando T esté restringido para ser un tipo de referencia. Sin ninguna restricción, puede comparar con null, pero solo con null, y esa comparación siempre será falsa para los tipos de valores que no admiten nulos.

En lugar de llamar a Equals, es mejor usar un IComparer<T> , y si no tiene más información, EqualityComparer<T>.Default es una buena opción:

public bool Compare<T>(T x, T y) { return EqualityComparer<T>.Default.Equals(x, y); }

Aparte de cualquier otra cosa, esto evita el boxeo / casting.


En general, EqualityComparer<T>.Default.Equals debe hacer el trabajo con cualquier cosa que implemente IEquatable<T> , o que tenga una implementación sensible de Equals .

Sin embargo, si == y Equals se implementan de manera diferente por alguna razón, entonces mi trabajo en operadores genéricos debería ser útil; Es compatible con las versiones de operador (entre otras):

  • Igual (valor T1, valor T2)
  • NotEqual (valor de T1, valor de T2)
  • GreaterThan (valor de T1, valor de T2)
  • LessThan (T value1, T value2)
  • GreaterThanOrEqual (T value1, T value2)
  • LessThanOrEqual (valor T1, valor T2)

Escribí la siguiente función mirando el último msdn. Puede comparar fácilmente dos objetos x y y :

static bool IsLessThan(T x, T y) { return ((IComparable)(x)).CompareTo(y) <= 0; }


Hay una entrada de MSDN Connect para esto here

La respuesta de Alex Turner comienza con:

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


La compilación no puede saber T no podría ser una estructura (tipo de valor). Así que hay que decir que solo puede ser de tipo de referencia, creo:

bool Compare<T>(T x, T y) where T : class { return x == y; }

Es porque si T podría ser un tipo de valor, podría haber casos en los que x == y estaría mal formado, en los casos en que un tipo no tenga un operador definido ==. Lo mismo sucederá con esto que es más obvio:

void CallFoo<T>(T x) { x.foo(); }

Eso también falla, porque podrías pasar un tipo T que no tendría una función foo. C # te obliga a asegurarte de que todos los tipos posibles siempre tengan una función foo. Eso es hecho por la cláusula where.


Parece que sin la restricción de clase:

bool Compare<T> (T x, T y) where T: class { return x == y; }

Uno debe darse cuenta de que mientras la class restringida es Equals en el operador == hereda de Object.Equals , mientras que la estructura reemplaza a ValueType.Equals .

Tenga en cuenta que:

bool Compare<T> (T x, T y) where T: struct { return x == y; }

También da el mismo error del compilador.

Hasta el momento no entiendo por qué el compilador rechaza una comparación de operador de igualdad de tipo de valor. Sin embargo, sé que esto funciona:

bool Compare<T> (T x, T y) { return x.Equals(y); }


Si desea asegurarse de que se llame a los operadores de su tipo personalizado, puede hacerlo a través de la reflexión. Simplemente obtenga el tipo usando su parámetro genérico y recupere MethodInfo para el operador deseado (por ejemplo, op_Equality, op_Inequality, op_LessThan ...).

var methodInfo = typeof (T).GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public);

Luego ejecute el operador utilizando el método Invoke de MethodInfo y pase los objetos como parámetros.

var result = (bool) methodInfo.Invoke(null, new object[] { object1, object2});

Esto invocará a su operador sobrecargado y no al definido por las restricciones aplicadas en el parámetro genérico. Podría no ser práctico, pero podría ser útil para realizar pruebas unitarias de sus operadores cuando se utiliza una clase base genérica que contiene un par de pruebas.


Tantas respuestas, y ni una sola explica el ¿POR QUÉ? (que Giovanni pidió explícitamente) ...

Los genéricos de .NET no actúan como plantillas de C ++. En las plantillas de C ++, la resolución de sobrecarga se produce después de que se conocen los parámetros de la plantilla real.

En los genéricos de .NET (incluido C #), la resolución de sobrecarga ocurre sin conocer los parámetros genéricos reales. La única información que el compilador puede usar para elegir la función a llamar proviene de restricciones de tipo en los parámetros genéricos.


bool Compare(T x, T y) where T : class { return x == y; }

Lo anterior funcionará porque == se cuida en el caso de tipos de referencia definidos por el usuario.
En el caso de tipos de valor, se puede anular ==. En cuyo caso, también se debería definir "! =".

Creo que esa podría ser la razón, no permite la comparación genérica usando "==".