2008-12-24 18 views
264

De acuerdo con la documentación del operador == en MSDN,¿No se puede aplicar el operador == a los tipos genéricos en C#?

Para los tipos de valor predefinido, el operador igualdad (==) devuelve verdadero si los valores de sus operandos son iguales, falso en caso contrario. Para los tipos de referencia que no sean cadenas, == 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 valores definidos por el usuario pueden sobrecargar el operador == (ver operador). Entonces, ¿pueden los tipos de referencia definidos por el usuario , aunque por defecto == se comporta como se describe para ambos tipos de referencia predefinidos y definidos por el usuario.

¿Por qué no se compila este fragmento de código?

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

me sale el error operador '==' no se puede aplicar a 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 acerca de los tipos de referencia. También pensé que la comparación bit por bit se proporciona para todos los tipos de valores, que ahora sé que es no correctos.

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

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

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); } } 
+0

Véase mi respuesta de nuevo por la respuesta a su pregunta de seguimiento. –

+0

Puede ser útil comprender que incluso sin genéricos, hay algunos tipos para los cuales ''==' no está permitido entre dos operandos del mismo tipo. Esto es cierto para los tipos 'struct' (excepto los tipos" predefinidos '') que no sobrecargan el 'operador =='. Como un ejemplo simple, intente esto: 'var map = typeof (string) .GetInterfaceMap (typeof (ICloneable)); Console.WriteLine (map == map);/* error en tiempo de compilación */' –

+0

Continuando mi propio comentario anterior. Por ejemplo (ver [otro hilo] (https://stackoverflow.com/questions/6379915/)), con 'var kvp1 = new KeyValuePair (); var kvp2 = kvp1; ', entonces no puede verificar' kvp1 == kvp2' porque 'KeyValuePair <,>' es una estructura, no es un tipo predefinido de C# y no sobrecarga el 'operador =='. Sin embargo, un ejemplo viene dado por 'var li = new List (); var e1 = li.GetEnumerator(); var e2 = e1; 'con el que no se puede hacer' e1 == e2' (aquí tenemos la estructura anidada 'List <>. Enumerator' (llamado' '" List'1 + Enumerator [T] "' 'por el tiempo de ejecución)) que no sobrecarga '=='). –

Respuesta

113

"... de forma predeterminada == se comporta como se describió 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; 
    } 

Seguimiento a la pregunta adicional, "Pero, en caso de que estoy usando un tipo de referencia, sería el operador del == utilizar el comparación de referencia predefinida, ¿o usaría la versión sobrecargada del operador si un tipo definiera una? "

Hubiera pensado 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, comparte.

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

en línea: sobrecargado == llamados

genérico:

Pulse cualquier tecla para continuar. . .

Seguimiento 2

sí quiero señalar que el cambio de mi método de comparación a

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

== hace que el operador sobrecargado para ser llamado. Supongo que sin especificar el tipo (como donde), 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 .

+0

Gracias. No noté que la declaración era solo sobre tipos de referencia. –

+4

Re: Seguimiento 2: en realidad, el compilador lo vinculará con el mejor método que encuentre, que en este caso es Test.op_Equal. Pero si tiene una clase que se deriva de Test y anula al operador, entonces se llamará al operador de Test. –

+4

Las buenas prácticas que me gustaría señalar es que siempre debe hacer la comparación real dentro de un método 'Igual 'reemplazado (no en el operador' == '). – jpbochi

12

La compilación no puede saber T no podría ser una estructura (tipo de valor). Así que hay que decir que sólo puede ser del tipo de referencia que pienso:

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

Es porque si T podría ser un tipo de valor, puede haber casos en los que x == y estaría mal formada - en los casos en que un tipo doesn no tiene un operador == definido. Lo mismo sucederá para este que es más obvio:

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

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

+1

Gracias por la aclaración. No sabía que los tipos de valores no admitían el operador == recién salido de la caja. –

+1

Hosam, probé con gmcs (mono), y siempre compara referencias. (es decir, no utiliza un operador opcionalmente definido == para T) –

+0

. Hay una advertencia con esta solución: el operador == no puede estar sobrecargado; [vea esta pregunta de StackOverflow] (http://stackoverflow.com/questions/2919232/using-overloaded-operator-in-a-generic-function). –

1
 

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

Lo anterior funcionará porque se tiene en cuenta == en caso de tipos de referencia definidos por el usuario.
En el caso de tipos de valores, == puede anularse. En cuyo caso, "! =" También debe definirse.

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

+2

Gracias. Creo que los tipos de referencia también pueden anular al operador. Pero la razón del fracaso ahora está clara. –

+1

El token '==' se usa para dos operadores diferentes. Si para los tipos de operandos dados existe una sobrecarga compatible del operador de igualdad, se usará esa sobrecarga. De lo contrario, si ambos operandos son tipos de referencia que son compatibles entre sí, se utilizará una comparación de referencia. Tenga en cuenta que en el método 'Comparar' anterior el compilador no puede decir que el primer significado se aplica, pero puede decir que el segundo significado se aplica, por lo que el símbolo' == 'usará el último * incluso si' T' sobrecarga la igualdad- verifique el operador (por ejemplo, si es de tipo 'String') *. – supercat

6

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 class constreñidos Equals en el operador == hereda de Object.Equals, mientras que la de una estructura anula 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.

Todavía no entiendo por qué el compilador rechaza tener una comparación de operador de igualdad de tipo de valor.Yo sé que es un hecho, sin embargo, que esto funciona:

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

sabes que soy un novato C# total. pero creo que falla porque el compilador no sabe qué hacer. como todavía no se conoce T, lo que se hace depende del tipo T si se permiten los tipos de valores. para las referencias, las referencias solo se comparan independientemente de T. si lo hace .Equals, entonces se acaba de llamar a .Equal. –

+0

pero si lo hace == en un tipo de valor, el tipo de valor no tiene que implementar ese operador necassary. –

+0

Eso tendría sentido, litb :) Es posible que las estructuras definidas por el usuario no se sobrecarguen ==, por lo tanto, el compilador falla. –

249

Como han dicho otros, que sólo funcionará cuando T es obligado a ser un tipo de referencia. Sin restricciones, puede comparar con nulo, pero solo nulo, y esa comparación siempre será falsa para los tipos de valores que no admiten nulos.

En lugar de llamar a igual, es mejor utilizar un IComparer<T> - y si usted 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/fundición.

+0

Gracias. Estaba tratando de escribir una clase contenedora simple, así que solo quería delegar la operación en el miembro real envuelto. Pero saber de EqualityComparer . Por supuesto ciertamente me agregó valor. :) –

+0

Menor de lado, Jon; Es posible que desee tener en cuenta el comentario re pobox vs yoda en mi publicación. –

+4

Buen consejo sobre el uso de EqualityComparer chakrit

34

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

Si, sin embargo, == y Equals se implementan de manera diferente por algún motivo, entonces mi trabajo en generic operators debería ser útil; que soporta el operador versiones de (entre otros):

  • Equal (T value1, T valor2)
  • NotEqual (T valor1, T valor2)
  • GreaterThan (T valor1, T valor2)
  • lessThan (valor1 T, T valor2)
  • GreaterThanOrEqual (T valor1, T valor2)
  • LessThanOrEqual (T valor1, T valor2)
+0

¡Biblioteca muy interesante! :) (Nota al margen: ¿Puedo sugerir el uso del enlace a www.yoda.arachsys.com, porque el pobox uno fue bloqueado por el firewall en mi lugar de trabajo? Es posible que otros puedan enfrentar el mismo problema.) –

+0

Interesante (la cosa pobox/yoda). Lo recordaré ... –

+0

La idea es que http://pobox.com/~skeet * siempre * apunta a mi sitio web, incluso si se mueve a otro lado. Tiendo a publicar enlaces a través de pobox.com por el bien de la posteridad, pero en este momento puedes * sustituir * a yoda.arachsys.com. –

4

hay una entrada de MSDN Connect para responder esta here

Alex Turner comienza con:

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

21

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

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

En genéricos .NET (incluido C#), la resolución de sobrecarga se produce 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.

+2

pero ¿por qué el compilador no puede tratarlos como un objeto genérico? después de todo '==' funciona para todos los tipos ya sean tipos de referencia o tipos de valores. Esa debería ser la pregunta a la que no creo que hayas respondido. – nawfal

+2

@nawfal: en realidad no, '==' no funciona para todos los tipos de valores. Más importante aún, no tiene el mismo significado para todos los tipos, por lo que el compilador no sabe qué hacer con él. –

+1

Ben, oh sí, me perdí las estructuras personalizadas que podemos crear sin ningún '=='. ¿Puedes incluir esa parte también en tu respuesta ya que supongo que ese es el punto principal aquí? – nawfal

4

Si desea asegurarse de que se llaman los operadores de su tipo personalizado, puede hacerlo a través de la reflexión. Simplemente obtenga el tipo utilizando su parámetro genérico y recupere el 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}); 

Este invocarán su operador sobrecargado y no el definido por las restricciones aplicadas en el parámetro genérico. Puede que no sea práctico, pero podría ser útil para probar las unidades de los operadores cuando se utiliza una clase base genérica que contiene un par de pruebas.

1

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

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

Puedes deshacerte de tus booleanos y escribir 'return ((IComparable) (x)). CompareTo (y) <= 0;' – aloisdg

2

Bueno, en mi caso yo quería unidad de prueba el operador de igualdad. Necesitaba llamar al código bajo los operadores de igualdad sin establecer explícitamente el tipo genérico. Los avisos para EqualityComparer no fueron útiles como EqualityComparer método llamado Equals, pero no el operador de igualdad.

He aquí cómo esto funciona con tipos genéricos construyendo un LINQ. Se llama el código correcto para == y != operadores:

/// <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); 
} 
Cuestiones relacionadas