with when structures structs recorrer initialize dotnetperls c# struct equals override

c# - when - Reemplazando el método Equals en Structs



structures in c# (6)

Agregando a las respuestas existentes.

Aún puede tener valores que aceptan valores de null si agrega un? después del nombre de la estructura (esto funciona para cada objeto de valor)

int?

La (MyStructName)variableName también se realiza llamando a (MyStructName)variableName

He buscado directrices generales para las estructuras, pero todo lo que puedo encontrar es para las clases.

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

public bool Equals(object obj)

parece que no hay nada que impida que el usuario de mi estructura intente 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? C # as palabra clave parece ser solo adecuado para los tipos de referencia.


En caso de que alguien se pregunte sobre el rendimiento alcanzado al encajonar la estructura en un objeto Nullable (para evitar la verificación del tipo doble de is y the cast), hay una sobrecarga no despreciable.

tl; dr : El uso is & cast 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 puede tener fallas de muchas maneras, aunque sí verifiqué que el código de referencia en sí mismo no estaba optimizado de manera extraña.

Mirando el IL, el método de doble verificació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

Verificación doble 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 Roman Reiner por detectar un error que realmente no me hacía quedar bien.


Gracias a algunas noticias en C # 7.0 hay una manera más fácil de lograr lo mismo que una respuesta aceptada:

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 favorito - lo mismo que la función cuerpo de 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 }


Supongo que si uno usa .NET 4.5 , uno puede usar la implementación predeterminada como se indica en la documentation :

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

ValueType.Equals : Value equality; ya sea una comparación directa byte por byte o una comparación campo por campo usando la reflexión.


Use el operador is :

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


struct MyStruct { public override bool Equals(object obj) { if (!(obj is MyStruct)) return false; MyStruct mys = (MyStruct) obj; // compare elements here } }