Подтвердить что ты не робот

Переопределение метода Equals в Structs

Я искал руководящие принципы для структур, но все, что я могу найти, это классы.

Сначала мне показалось, что мне не нужно проверять, был ли переданный объект пустым, поскольку структуры являются типами значений и не могут быть нулевыми. Но теперь, когда я задумываюсь об этом, как знак равенства,

public bool Equals(object obj)

Кажется, что нет ничего, что помешало бы моей моей структуре пытаться сравнить ее с произвольным ссылочным типом.

Мой второй момент касается кастинга, который я (думаю, я) должен сделать, прежде чем сравнивать свои частные поля в своей структуре. Как я должен использовать объект для своего типа структуры? Ключевое слово С# as похоже только для ссылочных типов.

4b9b3361

Ответ 1

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

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

   }

}

Ответ 2

Я полагаю, что если использовать .NET 4.5, можно использовать реализацию по умолчанию, как указано в документации:

Когда вы определяете свой собственный тип, этот тип наследует функциональность, определенную методом Equals своего базового типа.

ValueType.Equals: равенство значений; либо прямое побайтовое сравнение, либо полевое сравнение с использованием отражения.

Ответ 3

Благодаря сопоставлению с образцом в С# 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;
}

Ответ 4

В случае, если кто-то задается вопросом об ударе производительности бокса в структуре объекта 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

Реквизиты Роману Райнеру за то, что он заметил ошибку, которая действительно не заставляла меня выглядеть хорошо.

Ответ 5

Используйте оператор is:

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

Ответ 6

Добавление к существующим ответам.

У вас все еще могут быть значения NULL, если вы добавляете? после имени структуры (это работает для каждого объекта значения)

int?

Кастинг выполняется также путем вызова (MyStructName)variableName