C # - метод значений типа равенства - почему компилятор использует отражение? - PullRequest
15 голосов
/ 18 июня 2009

Я только что натолкнулся на нечто довольно странное для меня: когда вы используете метод Equals () для типа значения (и, если этот метод не был переопределен, конечно), вы получаете что-то очень очень медленное - поля сравниваются один к одному с помощью отражения! Как в:

public struct MyStruct{
   int i;
}

   (...)

   MyStruct s, t;
   s.i = 0;
   t.i = 1;
   if ( s.Equals( t ))   /*  s.i will be compared to t.i via reflection here. */
      (...)

Мой вопрос: почему компилятор C # не генерирует простой метод для сравнения типов значений? Что-то вроде (в определении MyStruct):

   public override bool Equals( Object o ){
      if ( this.i == o.i )
         return true;
      else
         return false;
   }

Компилятор знает, каковы поля MyStruct во время компиляции, почему он ожидает до времени выполнения для перечисления полей MyStruct?

Очень странно для меня.

Спасибо:)

ДОБАВЛЕНО : Извините, я просто понимаю, что, конечно, Equals - это не ключевое слово языка, а метод времени выполнения ... Компилятор совершенно не знает об этом методе. Так что здесь имеет смысл использовать отражение.

Ответы [ 3 ]

10 голосов
/ 18 июня 2009

Ниже приведен декомпилированный метод ValueType.Equals из mscorlib:

public override bool Equals(object obj)
{
    if (obj == null)
    {
        return false;
    }
    RuntimeType type = (RuntimeType) base.GetType();
    RuntimeType type2 = (RuntimeType) obj.GetType();
    if (type2 != type)
    {
        return false;
    }
    object a = this;
    if (CanCompareBits(this))
    {
        return FastEqualsCheck(a, obj);
    }
    FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
    for (int i = 0; i < fields.Length; i++)
    {
        object obj3 = ((RtFieldInfo) fields[i]).InternalGetValue(a, false);
        object obj4 = ((RtFieldInfo) fields[i]).InternalGetValue(obj, false);
        if (obj3 == null)
        {
            if (obj4 != null)
            {
                return false;
            }
        }
        else if (!obj3.Equals(obj4))
        {
            return false;
        }
    }
    return true;
}

Когда это возможно, будет проводиться побитовое сравнение (обратите внимание на CanCompareBits и FastEqualsCheck, оба из которых определены как InternalCall. JIT, вероятно, вставит соответствующий код здесь. Что касается того, почему он такой медленный, я не смог бы ' не скажу.

10 голосов
/ 18 июня 2009

Он не использует отражение , когда ему не нужно . Он просто сравнивает значения побитно в случае, если struct, если это возможно. Однако, если какой-либо из struct членов (или членов членов, любых потомков) переопределяет object.Equals и обеспечивает собственную реализацию, очевидно, он не может полагаться на побитовое сравнение для вычисления возвращаемого значения.

Причина, по которой он медленный, заключается в том, что параметр Equals имеет тип object, а типы значений должны быть помещены в квадрат, чтобы их обрабатывали как object. Упаковка включает в себя выделение памяти в куче, а память копирует тип значения в это место.

Вы можете вручную предоставить перегрузку для метода Equals, который принимает собственный struct в качестве параметра для предотвращения упаковки:

public bool Equals(MyStruct obj) {
     return obj.i == i;
}
3 голосов
/ 21 ноября 2013

Идея функции, сгенерированной компилятором, оправдана.

Думая об эффектах, я думаю, что команда разработчиков языка сделала это правильно. Сгенерированные компилятором методы, известные из C ++, трудны для понимания новичками. Давайте посмотрим, что произойдет в C # с автоматически сгенерированной struct.Equals:

Как и сейчас, концепция .Equals () проста:

  • Каждая структура наследует Equals из ValueType.
  • При переопределении применяется пользовательский метод Equals.

Если бы компилятор всегда создавал метод Equals, мы могли бы иметь:

  • Каждая структура наследует Equals от Object. (ValueType больше не будет реализовывать свою собственную версию)
  • Object.Equals теперь всегда (!) Переопределяется либо сгенерированным компилятором методом Equals, либо пользовательской реализацией

Теперь в нашей структуре есть автоматически сгенерированный метод переопределения, который программа чтения кода не видит! Итак, как вы узнаете, что базовый метод Object.Equals не применяется к вашей структуре? Изучая все случаи автоматически сгенерированных методов компилятора. И это как раз одна из трудностей изучения C ++.

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

При этом критические структуры производительности должны переопределять Equals. Код ниже показывает

3606 против 53 Миллисекунд измерено в .Net 4.5.1

Это увеличение производительности, безусловно, связано с отказом от виртуальных равных, но в любом случае, поэтому, если бы виртуальные Object.Equals назывались, усиление было бы намного ниже. Однако в критических случаях производительности не будет вызываться Object.Equals, поэтому здесь будет применяться усиление.

using System;
using System.Diagnostics;

struct A
{
    public int X;
    public int Y;
}

struct B : IEquatable<B>
{
    public bool Equals(B other)
    {
        return this.X == other.X && this.Y == other.Y;
    }

    public override bool Equals(object obj)
    {
        return obj is B && Equals((B)obj);
    }

    public int X;
    public int Y;
}


class Program
{
    static void Main(string[] args)
    {
        var N = 100000000;

        A a = new A();
        a.X = 73;
        a.Y = 42;
        A aa = new A();
        a.X = 173;
        a.Y = 142;

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < N; i++)
        {
            if (a.Equals(aa))
            {
                Console.WriteLine("never ever");
            }
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);

        B b = new B();
        b.X = 73;
        b.Y = 42;
        B bb = new B();
        b.X = 173;
        b.Y = 142;

        sw = Stopwatch.StartNew();
        for (int i = 0; i < N; i++)
        {
            if (b.Equals(bb))
            {
                Console.WriteLine("never ever");
            }
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
    }
}

см. Также http://blog.martindoms.com/2011/01/03/c-tip-override-equals-on-value-types-for-better-performance/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...