@id благодарит за решение, но что-то также скрыто в org.eclipse.jst.server.tomcat.core.prefs
Поэтому для решения проблемы
Tomcat 5.5
, который я заказываю, чтобы быть в состоянии использовать tomcat5.5 сервер, у Вас должен быть writeable catalina.policy файл, как упомянуто в [1 113]
Tomcat 6
, Чтобы быть в состоянии использовать tomcat6 сервер надлежащее решение, должен иметь пользовательский экземпляр tomcat6 сервера, как описано в [1 115]
, Моя конфигурация является Debian/Sid, Eclipse 3.4.1. Ganymede
Это реализация 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
. Однако я окончательно выяснил то, что хотел узнать (а я ленив), так что это работа на другой день.
По умолчанию нет равенства элементов, но для типов базовых значений ( float
, байт
, decimal
и т. Д.), спецификация языка требует побитового сравнения. Оптимизатор JIT оптимизирует это до правильных инструкций сборки, но технически это поведение аналогично функции C memcmp
.
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
] не следует рассматривать как переопределение, исправлено.
Это сложнее, чем кажется на первый взгляд. Короткий ответ:
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;
}
В большинстве обычных ситуаций это не имеет значения, но об этом следует знать.
Вот моя собственная попытка решить эту проблему. Это работает, но я не уверен, что рассмотрел все основы.
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;
}
}