2010-03-30 12 views
49

He buscado pautas generales para las estructuras, pero todo lo que puedo encontrar es para las clases.Reemplazando el método Equals en Structs

Al principio pensé que no tendría que comprobar para ver si el objeto pasado era nulo, ya que las estructuras son tipos de valores y no pueden ser nulas. Pero ahora que lo pienso de ella, ya que es igual a la firma es

public bool Equals(object obj) 

parece que no hay nada que impida que el usuario de mi struct estar tratando de compararlo con un tipo de referencia arbitrario.

Mi segundo punto se refiere al casting que (creo que) debo hacer antes de comparar mis campos privados en mi estructura. ¿Cómo se supone que voy a lanzar el objeto al tipo de mi estructura? La palabra clave as de C# parece solo adecuada para tipos de referencia.

+6

Sólo una nota que se les anima a evitar estructuras mutables en .Net. Está configurado, debe ajustarse a los tipos de referencia (clases) la mayor parte del tiempo, y usar estructuras solo raramente. –

+4

Yo en segundo lugar. Use estructuras inmutables * sin * subtipos. Entonces Equals y == deben ser iguales para un receptor dado (valor del lado izquierdo) donde la única diferencia en la implementación es Equals necesita una verificación 'is' y luego, por simplicidad, se distribuye a ==. Por lo tanto, ambos contratos se cumplen y las sorpresas se mitigan. –

+0

Sí, esta estructura es inmutable. Solo estoy comparando uno int. –

Respuesta

64
struct MyStruct 
{ 
    public override bool Equals(object obj) 
    { 
     if (!(obj is MyStruct)) 
      return false; 

     MyStruct mys = (MyStruct) obj; 
     // compare elements here 

    } 

} 
+1

¿Se puede agregar la palabra clave 'override' aquí para mayor claridad? Siempre es una buena práctica en Java, debe ser igual en C#. –

+1

Consulte también las pautas de Microsoft - http://msdn.microsoft.com/en-us/library/ms173147(v=vs.v.80).aspx – yoyo

+7

@JohanS En C# es más que solo una buena práctica, si omite 'anular 'el método realmente hace algo completamente diferente. – Pharap

5

Use el operador is:

public bool Equals(object obj) 
{ 
    if (obj is MyStruct) 
    { 
    var o = (MyStruct)obj; 
    ... 
    } 
} 
0

Agregando a las respuestas existentes.

¿Aún puede tener valores anulables si agrega un? después de que el nombre de estructura (esto funciona para todos los objetos de valor)

int? 

fundición se realiza también llamando (MyStructName)variableName

+2

Puede, pero los nulables tienen una penalización de rendimiento muy alto que costará más que cualquier beneficio que hubiera obtenido al usar "como" en lugar de "es". –

+0

@DanStory No sería tan rápido. Si le interesa echarle un vistazo a [esto] (http://stackoverflow.com/a/28281410), me gustaría saber si me he perdido algo. tl; dr: es + compilaciones un poco más agradables, pero no parece haber nada como una "penalización de muy alto rendimiento" como + boxeo. De hecho, no puedo hacer que la ejecución de is + cast sea confiablemente más rápida (a veces el método as + boxing tomará la delantera). – tne

+0

@DanStory Definitivamente estaba equivocado sobre ese comentario anterior. La penalización * es * alta (en comparación con la alternativa en una microbanda de todos modos). La misma respuesta vinculada fue editada. – tne

12

supongo, si uno está usando .NET 4,5, se puede utilizar la implementación predeterminada como se ha señalado en el documentation:

Cuando define su propio tipo, ese tipo hereda la funcionalidad definida por el método Equals de su tipo base.

ValueType.Equals: igualdad de valor; ya sea una comparación directa byte por byte o una comparación campo por campo usando la reflexión.

+1

En realidad es anterior a 4.5, no sé cuándo se agregó, pero definitivamente está disponible en 4. Aunque un comentario en MSDN parece indicar que puede no ser exacto para los tipos de coma flotante. – Pharap

+1

Consulte http://stackoverflow.com/q/1009394 para consideraciones de rendimiento. – tne

+0

@Pharap: Al definir 'Equals', Microsoft no dejó en claro cómo deberían ser las cosas" iguales "para que devuelva' verdadero'; hay algunos contextos en los que es útil probar valores de coma flotante para * equivalencia * (que cero positivo y negativo son numéricamente iguales no implica que sean equivalentes, ya que si xey son equivalentes, esto implica que 1/x = = 1/año, pero eso no es cierto para el cero positivo y negativo). Los valores de punto flotante en algunas estructuras se prueban para la equivalencia, pero no conozco ningún medio general para solicitar dicha prueba. – supercat

6

En caso de que alguien se está preguntando acerca del impacto en el rendimiento del boxeo la estructura de un objeto anulable (para evitar la comprobación de tipo doble de is y el elenco), no es una sobrecarga no despreciable.

tl; dr: Use is & en este escenario.

struct Foo : IEquatable<Foo> 
{ 
    public int a, b; 

    public Foo(int a, int b) 
    { 
     this.a = a; 
     this.b = b; 
    } 

    public override bool Equals(object obj) 
    { 
#if BOXING 
     var obj_ = obj as Foo?; 
     return obj_ != null && Equals(obj_.Value); 
#elif DOUBLECHECK 
     return obj is Foo && Equals((Foo)obj); 
#elif MAGIC 
     ? 
#endif 
    } 

    public bool Equals(Foo other) 
    { 
     return a == other.a && b == other.b; 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     RunBenchmark(new Foo(42, 43), new Foo(42, 43)); 
     RunBenchmark(new Foo(42, 43), new Foo(43, 44)); 
    } 

    static void RunBenchmark(object x, object y) 
    { 
     var sw = Stopwatch.StartNew(); 
     for (var i = 0; i < 100000000; i++) x.Equals(y); 
     sw.Stop(); 
     Console.WriteLine(sw.ElapsedMilliseconds); 
    } 
} 

Resultados:

BOXING 
EQ 8012 7973 7981 8000 
NEQ 7929 7715 7906 7888 

DOUBLECHECK 
EQ 3654 3650 3638 3605 
NEQ 3310 3301 3319 3297 

Advertencia: Esta prueba podría ser defectuoso en muchos aspectos, aunque se ha verificado que el código de prueba en sí no se ha optimizado de forma extraña.

Al observar el IL, el método de doble comprobación compila un poco más limpio.

boxeo IL:

.method public hidebysig virtual 
    instance bool Equals (
     object obj 
    ) cil managed 
{ 
    // Method begins at RVA 0x2060 
    // Code size 37 (0x25) 
    .maxstack 2 
    .locals init (
     [0] valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo> obj_ 
    ) 

    IL_0000: ldarg.1 
    IL_0001: isinst valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo> 
    IL_0006: unbox.any valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo> 
    IL_000b: stloc.0 
    IL_000c: ldloca.s obj_ 
    IL_000e: call instance bool valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo>::get_HasValue() 
    IL_0013: brfalse.s IL_0023 

    IL_0015: ldarg.0 
    IL_0016: ldloca.s obj_ 
    IL_0018: call instance !0 valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo>::get_Value() 
    IL_001d: call instance bool StructIEqualsImpl.Foo::Equals(valuetype StructIEqualsImpl.Foo) 
    IL_0022: ret 

    IL_0023: ldc.i4.0 
    IL_0024: ret 
} // end of method Foo::Equals 

doble comprobación IL:

.method public hidebysig virtual 
    instance bool Equals (
     object obj 
    ) cil managed 
{ 
    // Method begins at RVA 0x2060 
    // Code size 23 (0x17) 
    .maxstack 8 

    IL_0000: ldarg.1 
    IL_0001: isinst StructIEqualsImpl.Foo 
    IL_0006: brfalse.s IL_0015 

    IL_0008: ldarg.0 
    IL_0009: ldarg.1 
    IL_000a: unbox.any StructIEqualsImpl.Foo 
    IL_000f: call instance bool StructIEqualsImpl.Foo::Equals(valuetype StructIEqualsImpl.Foo) 
    IL_0014: ret 

    IL_0015: ldc.i4.0 
    IL_0016: ret 
} // end of method Foo::Equals 

Apoyos a Reiner romana para la detección de un error que realmente no me hacía quedar bien.

+0

¡Su prueba * es * defectuosa! Su punto de referencia es llamar al método 'Foo.Equals (Foo)'. 'Foo.Equals (object)' nunca se ejecuta. –

+0

@RomanReiner: Oh, la vergüenza. Obviamente, tuve la intención de lanzar los objetos y simplemente olvidé; con grandes consecuencias (los resultados reales son muy diferentes) - bueno, si alguien concede alguna importancia a microbenchmarks de todos modos. ¡Muchas gracias! – tne

1

Gracias a some news in C# 7.0 hay una manera más fácil de lograr la misma respuesta que aceptado:

struct MyStruct 
{ 
    public override bool Equals(object obj) 
    { 
     if (!(obj is MyStruct mys)) // type pattern here 
      return false; 

     return this.field1 == mys.field1 && this.field2 == mys.field2 // mys is already known here without explicit casting 
    } 
} 

O mi favorita - lo mismo que la función carrozado expresión:

struct MyStruct 
{ 
    public override bool Equals(object obj) => 
     obj is MyStruct mys 
      ? true // the initial "true" doesn't affect the overall boolean operation yet allows nice line aligning below 
       && this.field1 == mys.field1 
       && this.field2 == mys.field2 
      : false; // obj is not MyStruct 
} 
Cuestiones relacionadas