Что алгоритм используется memberwise тестом равенства в структурах.NET?

@id благодарит за решение, но что-то также скрыто в org.eclipse.jst.server.tomcat.core.prefs

Поэтому для решения проблемы

  • , близкое затмение
  • переходит к {каталогу рабочей области}/.metadata/.plugins/org.eclipse.core.runtime/.settings
  • , удаляют файлы org.eclipse.wst.server.core.prefs и org.eclipse.jst.server.tomcat.core.prefs

Tomcat 5.5

, который я заказываю, чтобы быть в состоянии использовать tomcat5.5 сервер, у Вас должен быть writeable catalina.policy файл, как упомянуто в [1 113]

  • http://dev.eclipse.org/newslists/news.eclipse.webtools/msg16795.html (=, добавляют ЧТЕНИЕ и полномочия ЗАПИСИ в файлы в каталоге "{$tomcat.home}/conf" (chmod - стабиловольт a+rw {$tomcat.home}/conf /*). Чтобы быть точнее, на файле "catalina.policy". После этого сервер Tomcat может быть добавлен в серверах Eclipse)
  • (битая ссылка) http://webui.sourcelabs.com/eclipse/issues/239179 и остановить tomcat5.5 прежде, чем ввести затмение, и запустился впоследствии.

Tomcat 6

, Чтобы быть в состоянии использовать tomcat6 сервер надлежащее решение, должен иметь пользовательский экземпляр tomcat6 сервера, как описано в [1 115]

  • /usr/share/doc/tomcat6-common/RUNNING.txt.gz
  • RUNNING.txt (в СЕТИ)

, Моя конфигурация является Debian/Sid, Eclipse 3.4.1. Ganymede

12
задан Damian Powell 5 November 2009 в 13:29
поделиться

4 ответа

Это реализация ValueType.Equals из общей языковой инфраструктуры общего источника (версия 2.0) .

public override bool Equals (Object obj) {
    BCLDebug.Perf(false, "ValueType::Equals is not fast.  "+
        this.GetType().FullName+" should override Equals(Object)");
    if (null==obj) {
        return false;
    }
    RuntimeType thisType = (RuntimeType)this.GetType();
    RuntimeType thatType = (RuntimeType)obj.GetType();

    if (thatType!=thisType) {
        return false;
    }

    Object thisObj = (Object)this;
    Object thisResult, thatResult;

    // if there are no GC references in this object we can avoid reflection 
    // and do a fast memcmp
    if (CanCompareBits(this))
        return FastEqualsCheck(thisObj, obj);

    FieldInfo[] thisFields = thisType.GetFields(
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

    for (int i=0; i<thisFields.Length; i++) {
        thisResult = ((RtFieldInfo)thisFields[i])
            .InternalGetValue(thisObj, false);
        thatResult = ((RtFieldInfo)thisFields[i])
            .InternalGetValue(obj, false);

        if (thisResult == null) {
            if (thatResult != null)
                return false;
        }
        else
        if (!thisResult.Equals(thatResult)) {
            return false;
        }
    }

    return true;
}

Интересно отметить, что это в значительной степени именно тот код, который показан в Reflector. Это меня удивило, потому что я думал, что SSCLI - это всего лишь эталонная реализация, а не окончательная библиотека. Опять же, я полагаю, что существует ограниченное количество способов реализовать этот относительно простой алгоритм.

Части, которые я хотел понять больше, - это вызовы CanCompareBits и FastEqualsCheck . Оба они реализованы как собственные методы, но их код также включен в SSCLI. Как видно из приведенных ниже реализаций, интерфейс командной строки просматривает определение класса объекта (через его таблицу методов), чтобы узнать, содержит ли он указатели на ссылочные типы и как распределяется память для объекта. Если ссылок нет и объект является непрерывным, то память сравнивается напрямую с помощью функции C memcmp .

// Return true if the valuetype does not contain pointer and is tightly packed
FCIMPL1(FC_BOOL_RET, ValueTypeHelper::CanCompareBits, Object* obj)
{
    WRAPPER_CONTRACT;
    STATIC_CONTRACT_SO_TOLERANT;

    _ASSERTE(obj != NULL);
    MethodTable* mt = obj->GetMethodTable();
    FC_RETURN_BOOL(!mt->ContainsPointers() && !mt->IsNotTightlyPacked());
}
FCIMPLEND

FCIMPL2(FC_BOOL_RET, ValueTypeHelper::FastEqualsCheck, Object* obj1,
    Object* obj2)
{
    WRAPPER_CONTRACT;
    STATIC_CONTRACT_SO_TOLERANT;

    _ASSERTE(obj1 != NULL);
    _ASSERTE(obj2 != NULL);
    _ASSERTE(!obj1->GetMethodTable()->ContainsPointers());
    _ASSERTE(obj1->GetSize() == obj2->GetSize());

    TypeHandle pTh = obj1->GetTypeHandle();

    FC_RETURN_BOOL(memcmp(obj1->GetData(),obj2->GetData(),pTh.GetSize()) == 0);
}
FCIMPLEND

Если бы я не был таким ленивым, я мог бы изучить реализацию ContainsPointers и IsNotTightlyPacked . Однако я окончательно выяснил то, что хотел узнать (а я ленив), так что это работа на другой день.

4
ответ дан 2 December 2019 в 07:21
поделиться

По умолчанию нет равенства элементов, но для типов базовых значений ( float , байт , decimal и т. Д.), спецификация языка требует побитового сравнения. Оптимизатор JIT оптимизирует это до правильных инструкций сборки, но технически это поведение аналогично функции C memcmp .

Некоторые примеры BCL

  • DateTime просто сравнивают свои внутренние InternalTicks поле члена, которое является длинным;
  • PointF сравнивает X и Y, как в (left.X == right.X) && (left.Y == right.Y) ;
  • Decimal не сравнивает внутренние поля, а возвращается к InternalImpl, что означает, что он находится во внутренней недоступной для просмотра части .NET (но вы можете проверить SSCLI);
  • Rectangle явно сравнивает каждое поле (Икс, y, width, height);
  • ModuleHandle использует свое переопределение Equals и многие другие, которые делают это;
  • SqlString и другие структуры SqlXXX используют его IComparable .Compare реализация;
  • Guid является самым странным в этом списке: у него есть собственный длинный список коротких замыканий if-операторов, сравнивающих каждое внутреннее поле ( _a to _k , all int) для неравенства, возвращая false при неравенстве. Если все не равны, возвращается истина.

Заключение

Этот список довольно произвольный, но я надеюсь, что он проливает свет на проблему: нет доступного метода по умолчанию, и даже BCL использует другой подход для каждого struct, в зависимости от ее назначения. Суть в том, что более поздние добавления чаще вызывают переопределение Equals или Icomparable.Compare , но это просто перемещает проблему в другой метод.

Другие способы:

] Вы можете использовать отражение для просмотра каждого поля, но это очень медленно. Вы также можете создать единственный метод расширения или статический помощник, который выполняет побитовое сравнение внутренних полей. Используйте StructLayout.Sequential , возьмите адрес и размер памяти и сравните блоки памяти. Это требует небезопасного кода, но это быстро, легко (и немного грязно).

Обновление: перефразирование, добавлены некоторые реальные примеры, добавлены новые выводы


Обновление: реализация поэлементного сравнения

Выше было очевидно небольшое недопонимание вопроса, но я оставляю его там, так как считаю, что в любом случае он имеет некоторую ценность для будущих посетителей. Вот более конкретный ответ:

Вот реализация поэлементного сравнения как для объектов, так и для типов значений, которое может проходить через все свойства, поля и перечисляемое содержимое рекурсивно, независимо от того, насколько глубоко. Он не тестировался, возможно, содержит опечатки, но компилируется нормально. См. Комментарии в коде для получения дополнительных сведений:

public static bool MemberCompare(object left, object right)
{
    if (Object.ReferenceEquals(left, right))
        return true;

    if (left == null || right == null)
        return false;

    Type type = left.GetType();
    if (type != right.GetType())
        return false;

    if(left as ValueType != null)
    {
        // do a field comparison, or use the override if Equals is implemented:
        return left.Equals(right);
    }

    // check for override:
    if (type != typeof(object)
        && type == type.GetMethod("Equals").DeclaringType)
    {
        // the Equals method is overridden, use it:
        return left.Equals(right);
    }

    // all Arrays, Lists, IEnumerable<> etc implement IEnumerable
    if (left as IEnumerable != null)
    {
        IEnumerator rightEnumerator = (right as IEnumerable).GetEnumerator();
        rightEnumerator.Reset();
        foreach (object leftItem in left as IEnumerable)
        {
            // unequal amount of items
            if (!rightEnumerator.MoveNext())
                return false;
            else
            {
                if (!MemberCompare(leftItem, rightEnumerator.Current))
                    return false;
            }                    
        }
    }
    else
    {
        // compare each property
        foreach (PropertyInfo info in type.GetProperties(
            BindingFlags.Public | 
            BindingFlags.NonPublic | 
            BindingFlags.Instance | 
            BindingFlags.GetProperty))
        {
            // TODO: need to special-case indexable properties
            if (!MemberCompare(info.GetValue(left, null), info.GetValue(right, null)))
                return false;
        }

        // compare each field
        foreach (FieldInfo info in type.GetFields(
            BindingFlags.GetField |
            BindingFlags.NonPublic |
            BindingFlags.Public |
            BindingFlags.Instance))
        {
            if (!MemberCompare(info.GetValue(left), info.GetValue(right)))
                return false;
        }
    }
    return true;
}

Обновление: исправлено несколько ошибок, добавлено использование переопределенного Equals , если и только если доступно
Обновление: object.Equals ] не следует рассматривать как переопределение, исправлено.

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

public static bool MemberCompare(object left, object right)
{
    if (Object.ReferenceEquals(left, right))
        return true;

    if (left == null || right == null)
        return false;

    Type type = left.GetType();
    if (type != right.GetType())
        return false;

    if(left as ValueType != null)
    {
        // do a field comparison, or use the override if Equals is implemented:
        return left.Equals(right);
    }

    // check for override:
    if (type != typeof(object)
        && type == type.GetMethod("Equals").DeclaringType)
    {
        // the Equals method is overridden, use it:
        return left.Equals(right);
    }

    // all Arrays, Lists, IEnumerable<> etc implement IEnumerable
    if (left as IEnumerable != null)
    {
        IEnumerator rightEnumerator = (right as IEnumerable).GetEnumerator();
        rightEnumerator.Reset();
        foreach (object leftItem in left as IEnumerable)
        {
            // unequal amount of items
            if (!rightEnumerator.MoveNext())
                return false;
            else
            {
                if (!MemberCompare(leftItem, rightEnumerator.Current))
                    return false;
            }                    
        }
    }
    else
    {
        // compare each property
        foreach (PropertyInfo info in type.GetProperties(
            BindingFlags.Public | 
            BindingFlags.NonPublic | 
            BindingFlags.Instance | 
            BindingFlags.GetProperty))
        {
            // TODO: need to special-case indexable properties
            if (!MemberCompare(info.GetValue(left, null), info.GetValue(right, null)))
                return false;
        }

        // compare each field
        foreach (FieldInfo info in type.GetFields(
            BindingFlags.GetField |
            BindingFlags.NonPublic |
            BindingFlags.Public |
            BindingFlags.Instance))
        {
            if (!MemberCompare(info.GetValue(left), info.GetValue(right)))
                return false;
        }
    }
    return true;
}

Обновление: исправлено несколько ошибок, добавлено использование переопределенного Equals , если и только если доступно
Обновление: object.Equals ] не следует рассматривать как переопределение, исправлено.

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

public static bool MemberCompare(object left, object right)
{
    if (Object.ReferenceEquals(left, right))
        return true;

    if (left == null || right == null)
        return false;

    Type type = left.GetType();
    if (type != right.GetType())
        return false;

    if(left as ValueType != null)
    {
        // do a field comparison, or use the override if Equals is implemented:
        return left.Equals(right);
    }

    // check for override:
    if (type != typeof(object)
        && type == type.GetMethod("Equals").DeclaringType)
    {
        // the Equals method is overridden, use it:
        return left.Equals(right);
    }

    // all Arrays, Lists, IEnumerable<> etc implement IEnumerable
    if (left as IEnumerable != null)
    {
        IEnumerator rightEnumerator = (right as IEnumerable).GetEnumerator();
        rightEnumerator.Reset();
        foreach (object leftItem in left as IEnumerable)
        {
            // unequal amount of items
            if (!rightEnumerator.MoveNext())
                return false;
            else
            {
                if (!MemberCompare(leftItem, rightEnumerator.Current))
                    return false;
            }                    
        }
    }
    else
    {
        // compare each property
        foreach (PropertyInfo info in type.GetProperties(
            BindingFlags.Public | 
            BindingFlags.NonPublic | 
            BindingFlags.Instance | 
            BindingFlags.GetProperty))
        {
            // TODO: need to special-case indexable properties
            if (!MemberCompare(info.GetValue(left, null), info.GetValue(right, null)))
                return false;
        }

        // compare each field
        foreach (FieldInfo info in type.GetFields(
            BindingFlags.GetField |
            BindingFlags.NonPublic |
            BindingFlags.Public |
            BindingFlags.Instance))
        {
            if (!MemberCompare(info.GetValue(left), info.GetValue(right)))
                return false;
        }
    }
    return true;
}

Обновление: исправлено несколько ошибок, добавлено использование переопределенного Equals , если и только если доступно
Обновление: object.Equals ] не следует рассматривать как переопределение, исправлено.

13
ответ дан 2 December 2019 в 07:21
поделиться

Это сложнее, чем кажется на первый взгляд. Короткий ответ:

public bool MyEquals(object obj1, object obj2)
{
  if(obj1==null || obj2==null)
    return obj1==obj2;
  else if(...)
    ...  // Your custom code here
  else if(obj1.GetType().IsValueType)
    return
      obj1.GetType()==obj2.GetType() &&
      !struct1.GetType().GetFields(ALL_FIELDS).Any(field =>
       !MyEquals(field.GetValue(struct1), field.GetValue(struct2)));
  else
    return object.Equals(obj1, obj2);
}

const BindingFlags ALL_FIELDS =
  BindingFlags.Instance |
  BindingFlags.Public |
  BindingFlags.NonPublic;

Однако это еще не все. Вот подробности:

Если вы объявляете структуру и не переопределяете .Equals (), NET Framework будет использовать одну из двух разных стратегий в зависимости от того, имеет ли ваша структура только «простые» типы значений (определяется «простой» ниже):

Если структура содержит только "простые" типы значений, в основном выполняется побитовое сравнение:

strncmp((byte*)&struct1, (byte*)&struct2, Marshal.Sizeof(struct1));

Если структура содержит ссылки или не "простые" типы значений, каждое объявленное поле сравнивается как с объектом .Equals ():

struct1.GetType()==struct2.GetType() &&
!struct1.GetType().GetFields(ALL_FIELDS).Any(field =>
  !object.Equals(field.GetValue(struct1), field.GetValue(struct2)));

Что считается "простым" типом? Из моих тестов кажется, что это любой базовый скалярный тип (int, long, decimal, double и т.д.), плюс любая структура, которая не имеет переопределения .Equals и содержит только " типы (рекурсивно).

Это имеет несколько интересных ответвлений. Например, в этом коде:

struct DoubleStruct
{
  public double value;
}

public void TestDouble()
{
  var test1 = new DoubleStruct { value = 1 / double.PositiveInfinity };
  var test2 = new DoubleStruct { value = 1 / double.NegativeInfinity };

  bool valueEqual = test1.value.Equals(test2.value);
  bool structEqual = test1.Equals(test2);

  MessageBox.Show("valueEqual=" + valueEqual + ", structEqual=" + structEqual);
}

вы ожидаете, что valueEqual всегда будет идентичным structEqual, независимо от того, что было присвоено test1.value и test2.value. Это не тот случай!

Причина этого удивительного результата в том, что double.Equals () учитывает некоторые сложности кодирования IEEE 754, такие как множественные представления NaN и нуля, но побитовое сравнение - нет. Поскольку double считается простым типом, structEqual возвращает false, когда биты различны, даже когда valueEqual возвращает true.

В приведенном выше примере использовались альтернативные представления нуля, но это также может происходить с несколькими значениями NaN:

...
  var test1 = new DoubleStruct { value = CreateNaN(1) };
  var test2 = new DoubleStruct { value = CreateNaN(2) };
...
public unsafe double CreateNaN(byte lowByte)
{
  double result = double.NaN;
  ((byte*)&result)[0] = lowByte;
  return result;
}

В большинстве обычных ситуаций это не имеет значения, но об этом следует знать.

типы (рекурсивно).

Это имеет несколько интересных ответвлений. Например, в этом коде:

struct DoubleStruct
{
  public double value;
}

public void TestDouble()
{
  var test1 = new DoubleStruct { value = 1 / double.PositiveInfinity };
  var test2 = new DoubleStruct { value = 1 / double.NegativeInfinity };

  bool valueEqual = test1.value.Equals(test2.value);
  bool structEqual = test1.Equals(test2);

  MessageBox.Show("valueEqual=" + valueEqual + ", structEqual=" + structEqual);
}

вы ожидаете, что valueEqual всегда будет идентичным structEqual, независимо от того, что было присвоено test1.value и test2.value. Это не тот случай!

Причина этого удивительного результата в том, что double.Equals () учитывает некоторые сложности кодирования IEEE 754, такие как множественные представления NaN и нуля, но побитовое сравнение - нет. Поскольку double считается простым типом, structEqual возвращает false, когда биты различны, даже когда valueEqual возвращает true.

В приведенном выше примере использовались альтернативные представления нуля, но это также может происходить с несколькими значениями NaN:

...
  var test1 = new DoubleStruct { value = CreateNaN(1) };
  var test2 = new DoubleStruct { value = CreateNaN(2) };
...
public unsafe double CreateNaN(byte lowByte)
{
  double result = double.NaN;
  ((byte*)&result)[0] = lowByte;
  return result;
}

В большинстве обычных ситуаций это не имеет значения, но об этом следует знать.

Это имеет несколько интересных ответвлений. Например, в этом коде:

struct DoubleStruct
{
  public double value;
}

public void TestDouble()
{
  var test1 = new DoubleStruct { value = 1 / double.PositiveInfinity };
  var test2 = new DoubleStruct { value = 1 / double.NegativeInfinity };

  bool valueEqual = test1.value.Equals(test2.value);
  bool structEqual = test1.Equals(test2);

  MessageBox.Show("valueEqual=" + valueEqual + ", structEqual=" + structEqual);
}

вы ожидаете, что valueEqual всегда будет идентичным structEqual, независимо от того, что было присвоено test1.value и test2.value. Это не тот случай!

Причина этого удивительного результата в том, что double.Equals () учитывает некоторые сложности кодирования IEEE 754, такие как множественные представления NaN и нуля, но побитовое сравнение - нет. Поскольку double считается простым типом, structEqual возвращает false, когда биты различны, даже когда valueEqual возвращает true.

В приведенном выше примере использовались альтернативные представления нуля, но это также может происходить с несколькими значениями NaN:

...
  var test1 = new DoubleStruct { value = CreateNaN(1) };
  var test2 = new DoubleStruct { value = CreateNaN(2) };
...
public unsafe double CreateNaN(byte lowByte)
{
  double result = double.NaN;
  ((byte*)&result)[0] = lowByte;
  return result;
}

В большинстве обычных ситуаций это не имеет значения, но об этом следует знать.

Это имеет несколько интересных ответвлений. Например, в этом коде:

struct DoubleStruct
{
  public double value;
}

public void TestDouble()
{
  var test1 = new DoubleStruct { value = 1 / double.PositiveInfinity };
  var test2 = new DoubleStruct { value = 1 / double.NegativeInfinity };

  bool valueEqual = test1.value.Equals(test2.value);
  bool structEqual = test1.Equals(test2);

  MessageBox.Show("valueEqual=" + valueEqual + ", structEqual=" + structEqual);
}

вы ожидаете, что valueEqual всегда будет идентичным structEqual, независимо от того, что было присвоено test1.value и test2.value. Это не тот случай!

Причина этого удивительного результата в том, что double.Equals () учитывает некоторые сложности кодирования IEEE 754, такие как множественные представления NaN и нуля, но побитовое сравнение - нет. Поскольку double считается простым типом, structEqual возвращает false, когда биты различны, даже когда valueEqual возвращает true.

В приведенном выше примере использовались альтернативные представления нуля, но это также может происходить с несколькими значениями NaN:

...
  var test1 = new DoubleStruct { value = CreateNaN(1) };
  var test2 = new DoubleStruct { value = CreateNaN(2) };
...
public unsafe double CreateNaN(byte lowByte)
{
  double result = double.NaN;
  ((byte*)&result)[0] = lowByte;
  return result;
}

В большинстве обычных ситуаций это не имеет значения, но об этом следует знать.

Например, в этом коде:

struct DoubleStruct
{
  public double value;
}

public void TestDouble()
{
  var test1 = new DoubleStruct { value = 1 / double.PositiveInfinity };
  var test2 = new DoubleStruct { value = 1 / double.NegativeInfinity };

  bool valueEqual = test1.value.Equals(test2.value);
  bool structEqual = test1.Equals(test2);

  MessageBox.Show("valueEqual=" + valueEqual + ", structEqual=" + structEqual);
}

вы ожидаете, что valueEqual всегда будет идентичным structEqual, независимо от того, что было присвоено test1.value и test2.value. Это не так!

Причина этого удивительного результата в том, что double.Equals () принимает во внимание некоторые сложности кодирования IEEE 754, такие как множественные представления NaN и нуля, но побитовое сравнение - нет. Поскольку double считается простым типом, structEqual возвращает false, когда биты различны, даже когда valueEqual возвращает true.

В приведенном выше примере использовались альтернативные представления нуля, но это также может происходить с несколькими значениями NaN:

...
  var test1 = new DoubleStruct { value = CreateNaN(1) };
  var test2 = new DoubleStruct { value = CreateNaN(2) };
...
public unsafe double CreateNaN(byte lowByte)
{
  double result = double.NaN;
  ((byte*)&result)[0] = lowByte;
  return result;
}

В большинстве обычных ситуаций это не имеет значения, но об этом следует знать.

Например, в этом коде:

struct DoubleStruct
{
  public double value;
}

public void TestDouble()
{
  var test1 = new DoubleStruct { value = 1 / double.PositiveInfinity };
  var test2 = new DoubleStruct { value = 1 / double.NegativeInfinity };

  bool valueEqual = test1.value.Equals(test2.value);
  bool structEqual = test1.Equals(test2);

  MessageBox.Show("valueEqual=" + valueEqual + ", structEqual=" + structEqual);
}

вы ожидаете, что valueEqual всегда будет идентичным structEqual, независимо от того, что было присвоено test1.value и test2.value. Это не тот случай!

Причина этого удивительного результата в том, что double.Equals () учитывает некоторые сложности кодирования IEEE 754, такие как множественные представления NaN и нуля, но побитовое сравнение - нет. Поскольку double считается простым типом, structEqual возвращает false, когда биты различны, даже когда valueEqual возвращает true.

В приведенном выше примере использовались альтернативные представления нуля, но это также может происходить с несколькими значениями NaN:

...
  var test1 = new DoubleStruct { value = CreateNaN(1) };
  var test2 = new DoubleStruct { value = CreateNaN(2) };
...
public unsafe double CreateNaN(byte lowByte)
{
  double result = double.NaN;
  ((byte*)&result)[0] = lowByte;
  return result;
}

В большинстве обычных ситуаций это не имеет значения, но об этом следует знать.

struct DoubleStruct
{
  public double value;
}

public void TestDouble()
{
  var test1 = new DoubleStruct { value = 1 / double.PositiveInfinity };
  var test2 = new DoubleStruct { value = 1 / double.NegativeInfinity };

  bool valueEqual = test1.value.Equals(test2.value);
  bool structEqual = test1.Equals(test2);

  MessageBox.Show("valueEqual=" + valueEqual + ", structEqual=" + structEqual);
}

можно было бы ожидать, что valueEqual всегда будет идентично structEqual, независимо от того, что было присвоено test1.value и test2.value. Это не тот случай!

Причина этого удивительного результата в том, что double.Equals () учитывает некоторые сложности кодирования IEEE 754, такие как множественные представления NaN и нуля, но побитовое сравнение - нет. Поскольку double считается простым типом, structEqual возвращает false, когда биты различны, даже когда valueEqual возвращает true.

В приведенном выше примере использовались альтернативные представления нуля, но это также может происходить с несколькими значениями NaN:

...
  var test1 = new DoubleStruct { value = CreateNaN(1) };
  var test2 = new DoubleStruct { value = CreateNaN(2) };
...
public unsafe double CreateNaN(byte lowByte)
{
  double result = double.NaN;
  ((byte*)&result)[0] = lowByte;
  return result;
}

В большинстве обычных ситуаций это не имеет значения, но об этом следует знать.

struct DoubleStruct
{
  public double value;
}

public void TestDouble()
{
  var test1 = new DoubleStruct { value = 1 / double.PositiveInfinity };
  var test2 = new DoubleStruct { value = 1 / double.NegativeInfinity };

  bool valueEqual = test1.value.Equals(test2.value);
  bool structEqual = test1.Equals(test2);

  MessageBox.Show("valueEqual=" + valueEqual + ", structEqual=" + structEqual);
}

можно было бы ожидать, что valueEqual всегда будет идентично structEqual, независимо от того, что было присвоено test1.value и test2.value. Это не так!

Причина этого удивительного результата в том, что double.Equals () принимает во внимание некоторые сложности кодирования IEEE 754, такие как множественные представления NaN и нуля, но побитовое сравнение - нет. Поскольку double считается простым типом, structEqual возвращает false, когда биты различны, даже когда valueEqual возвращает true.

В приведенном выше примере использовались альтернативные представления нуля, но это также может происходить с несколькими значениями NaN:

...
  var test1 = new DoubleStruct { value = CreateNaN(1) };
  var test2 = new DoubleStruct { value = CreateNaN(2) };
...
public unsafe double CreateNaN(byte lowByte)
{
  double result = double.NaN;
  ((byte*)&result)[0] = lowByte;
  return result;
}

В большинстве обычных ситуаций это не имеет значения, но об этом следует знать.

Причина этого удивительного результата заключается в том, что double.Equals () учитывает некоторые сложности кодирования IEEE 754, такие как множественные представления NaN и нуля, но побитовое сравнение - нет. Поскольку double считается простым типом, structEqual возвращает false, когда биты различны, даже когда valueEqual возвращает true.

В приведенном выше примере использовались альтернативные представления нуля, но это также может происходить с несколькими значениями NaN:

...
  var test1 = new DoubleStruct { value = CreateNaN(1) };
  var test2 = new DoubleStruct { value = CreateNaN(2) };
...
public unsafe double CreateNaN(byte lowByte)
{
  double result = double.NaN;
  ((byte*)&result)[0] = lowByte;
  return result;
}

В большинстве обычных ситуаций это не имеет значения, но об этом следует знать.

Причина этого удивительного результата заключается в том, что double.Equals () учитывает некоторые сложности кодирования IEEE 754, такие как множественные представления NaN и нуля, но побитовое сравнение - нет. Поскольку double считается простым типом, structEqual возвращает false, когда биты различны, даже когда valueEqual возвращает true.

В приведенном выше примере использовались альтернативные представления нуля, но это также может происходить с несколькими значениями NaN:

...
  var test1 = new DoubleStruct { value = CreateNaN(1) };
  var test2 = new DoubleStruct { value = CreateNaN(2) };
...
public unsafe double CreateNaN(byte lowByte)
{
  double result = double.NaN;
  ((byte*)&result)[0] = lowByte;
  return result;
}

В большинстве обычных ситуаций это не имеет значения, но об этом следует знать.

2
ответ дан 2 December 2019 в 07:21
поделиться

Вот моя собственная попытка решить эту проблему. Это работает, но я не уверен, что рассмотрел все основы.

public class MemberwiseEqualityComparer : IEqualityComparer
{
    public bool Equals(object x, object y)
    {
        // ----------------------------------------------------------------
        // 1. If exactly one is null, return false.
        // 2. If they are the same reference, then they must be equal by
        //    definition.
        // 3. If the objects are both IEnumerable, return the result of
        //    comparing each item.
        // 4. If the objects are equatable, return the result of comparing
        //    them.
        // 5. If the objects are different types, return false.
        // 6. Iterate over the public properties and compare them. If there
        //    is a pair that are not equal, return false.
        // 7. Return true.
        // ----------------------------------------------------------------

        //
        // 1. If exactly one is null, return false.
        //
        if (null == x ^ null == y) return false;

        //
        // 2. If they are the same reference, then they must be equal by
        //    definition.
        //
        if (object.ReferenceEquals(x, y)) return true;

        //
        // 3. If the objects are both IEnumerable, return the result of
        //    comparing each item.
        // For collections, we want to compare the contents rather than
        // the properties of the collection itself so we check if the
        // classes are IEnumerable instances before we check to see that
        // they are the same type.
        //
        if (x is IEnumerable && y is IEnumerable && false == x is string)
        {
            return contentsAreEqual((IEnumerable)x, (IEnumerable)y);
        }

        //
        // 4. If the objects are equatable, return the result of comparing
        //    them.
        // We are assuming that the type of X implements IEquatable<> of itself
        // (see below) which is true for the numeric types and string.
        // e.g.: public class TypeOfX : IEquatable<TypeOfX> { ... }
        //
        var xType = x.GetType();
        var yType = y.GetType();
        var equatableType = typeof(IEquatable<>).MakeGenericType(xType);
        if (equatableType.IsAssignableFrom(xType)
            && xType.IsAssignableFrom(yType))
        {
            return equatablesAreEqual(equatableType, x, y);
        }

        //
        // 5. If the objects are different types, return false.
        //
        if (xType != yType) return false;

        //
        // 6. Iterate over the public properties and compare them. If there
        //    is a pair that are not equal, return false.
        //
        if (false == propertiesAndFieldsAreEqual(x, y)) return false;

        //
        // 7. Return true.
        //
        return true;
    }

    public int GetHashCode(object obj)
    {
        return null != obj ? obj.GetHashCode() : 0;
    }

    private bool contentsAreEqual(IEnumerable enumX, IEnumerable enumY)
    {
        var enumOfObjX = enumX.OfType<object>();
        var enumOfObjY = enumY.OfType<object>();

        if (enumOfObjX.Count() != enumOfObjY.Count()) return false;

        var contentsAreEqual = enumOfObjX
            .Zip(enumOfObjY) // Custom Zip extension which returns
                             // Pair<TFirst,TSecond>. Similar to .NET 4's Zip
                             // extension.
            .All(pair => Equals(pair.First, pair.Second))
            ;

        return contentsAreEqual;
    }

    private bool equatablesAreEqual(Type equatableType, object x, object y)
    {
        var equalsMethod = equatableType.GetMethod("Equals");
        var equal = (bool)equalsMethod.Invoke(x, new[] { y });
        return equal;
    }

    private bool propertiesAndFieldsAreEqual(object x, object y)
    {
        const BindingFlags bindingFlags
            = BindingFlags.Public | BindingFlags.Instance;

        var propertyValues = from pi in x.GetType()
                                         .GetProperties(bindingFlags)
                                         .AsQueryable()
                             where pi.CanRead
                             select new
                             {
                                 Name   = pi.Name,
                                 XValue = pi.GetValue(x, null),
                                 YValue = pi.GetValue(y, null),
                             };

        var fieldValues = from fi in x.GetType()
                                      .GetFields(bindingFlags)
                                      .AsQueryable()
                          select new
                          {
                              Name   = fi.Name,
                              XValue = fi.GetValue(x),
                              YValue = fi.GetValue(y),
                          };

        var propertiesAreEqual = propertyValues.Union(fieldValues)
            .All(v => Equals(v.XValue, v.YValue))
            ;

        return propertiesAreEqual;
    }
}
2
ответ дан 2 December 2019 в 07:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: