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