web-dev-qa-db-ja.com

StructsでEqualsメソッドをオーバーライドする

構造体のオーバーライドガイドラインを探しましたが、見つけることができるのはクラスだけです。

構造体は値型であり、nullにはできないため、最初は、渡されたオブジェクトがnullであるかどうかを確認する必要はないと考えました。しかし今、私はそれを考えるようになりました、等しい署名は

public bool Equals(object obj)

私の構造体のユーザーが任意の参照型と比較しようとするのを妨げるものは何もないようです。

2番目のポイントは、構造体のプライベートフィールドを比較する前に作成しなければならないキャストです。オブジェクトを構造体の型にキャストするにはどうすればよいですか? C#のasキーワードは、参照型にのみ適しているようです。

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

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

   }

}
73
James Curran

.NET4.5を使用している場合、 documentation に記載されているデフォルトの実装を使用できます。

独自の型を定義すると、その型はその基本型のEqualsメソッドで定義された機能を継承します。

ValueType.Equals:値が等しい;直接バイト単位の比較またはリフレクションを使用したフィールド単位の比較のいずれか。

13

C#7.0のいくつかのニュース のおかげで、受け入れられた答えと同じことを達成する簡単な方法があります。

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

または私のお気に入り-式の身体機能と同じ:

struct MyStruct 
{
    public override bool Equals(object obj) => 
        obj is MyStruct mys
            && mys.field1 == this.field1
            && mys.field2 == this.field2;
}
7
ensisNoctis

isとキャストからの二重型チェックを回避するために)Nullableオブジェクトで構造体をボクシングすることのパフォーマンスヒットについて誰かが疑問に思う場合は、is無視できないオーバーヘッド。

tl; dr:このシナリオでは、isを使用してキャストします。

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

結果:

BOXING
EQ  8012    7973    7981    8000
NEQ 7929    7715    7906    7888

DOUBLECHECK
EQ  3654    3650    3638    3605
NEQ 3310    3301    3319    3297

警告:このテストには多くの点で欠陥があるかもしれませんが、ベンチマークコード自体が奇妙な方法で最適化されていないことを確認しました。

ILを見ると、ダブルチェックメソッドは少しきれいにコンパイルされます。

ボクシング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

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

Roman Reinerに、私を本当に見栄えよくしない間違いを見つけてくれました。

6
tne

is演算子を使用します。

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

既存の回答に追加します。

?を追加すると、null許容値を持つことができます。構造体名の後(これはすべての値オブジェクトに対して機能します)

int?

キャストは(MyStructName)variableNameを呼び出すことでも行われます

0
jpabluz