주석이있는 예는 다음과 같습니다.
class Program
{
// first version of structure
public struct D1
{
public double d;
public int f;
}
// during some changes in code then we got D2 from D1
// Field f type became double while it was int before
public struct D2
{
public double d;
public double f;
}
static void Main(string[] args)
{
// Scenario with the first version
D1 a = new D1();
D1 b = new D1();
a.f = b.f = 1;
a.d = 0.0;
b.d = -0.0;
bool r1 = a.Equals(b); // gives true, all is ok
// The same scenario with the new one
D2 c = new D2();
D2 d = new D2();
c.f = d.f = 1;
c.d = 0.0;
d.d = -0.0;
bool r2 = c.Equals(d); // false! this is not the expected result
}
}
그래서 이것에 대해 어떻게 생각하십니까?
답변
버그는 다음 두 줄에 있습니다 System.ValueType
: (참조 소스에 들어갔습니다)
if (CanCompareBits(this))
return FastEqualsCheck(thisObj, obj);
(두 방법 모두 [MethodImpl(MethodImplOptions.InternalCall)]
)
모든 필드의 너비가 8 바이트 인 경우 CanCompareBits
실수로 true를 반환하여 서로 다르지만 의미 적으로 동일한 두 값을 비트 단위로 비교합니다.
하나 이상의 필드가 8 바이트 너비가 아닌 경우 CanCompareBits
false를 반환하고 코드는 리플렉션을 사용하여 필드를 반복하고 Equals
각 값을 호출 -0.0
합니다 0.0
.
CanCompareBits
SSCLI 의 소스는 다음과 같습니다 .
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
답변
http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx 에서 답을 찾았습니다 .
핵심 부분은에 대한 소스 주석으로 CanCompareBits
, 스타일 비교 ValueType.Equals
사용 여부를 결정하는 데 사용됩니다 memcmp
.
CanCompareBits의 의견은 “값 유형에 포인터가 포함되어 있지 않고 압축되어 있으면 true를 반환합니다”라고 말합니다. FastEqualsCheck는 “memcmp”를 사용하여 비교 속도를 높입니다.
저자는 OP에 의해 설명 된 문제를 정확하게 진술합니다.
float 만 포함하는 구조가 있다고 가정하십시오. 하나에 +0.0이 포함되고 다른 하나에 -0.0이 포함되면 어떻게됩니까? 그것들은 동일해야하지만 기본 이진 표현은 다릅니다. Equals 메서드를 재정의하는 다른 구조를 중첩하면 해당 최적화도 실패합니다.
답변
Vilx의 추측은 맞습니다. “CanCompareBits”가 수행하는 작업은 해당 값 유형이 메모리에 “꽉 조여져 있는지”확인하는 것입니다. 꽉 채워진 구조체는 구조를 구성하는 이진 비트를 간단히 비교하여 비교됩니다. 느슨하게 패킹 된 구조는 모든 멤버에서 Equals를 호출하여 비교됩니다.
이것은 SLaks가 두 배인 구조체로 재현한다는 관찰을 설명합니다. 그러한 구조체는 항상 단단히 포장되어 있습니다.
불행히도 여기서 보았 듯이, double의 비트 비교와 double의 Equals 비교가 다른 결과를 나타 내기 때문에 의미상의 차이가 발생합니다.
답변
반 답변 :
리플렉터는 ValueType.Equals()
다음과 같은 작업을 수행합니다.
if (CanCompareBits(this))
return FastEqualsCheck(this, obj);
else
// Use reflection to step through each member and call .Equals() on each one.
불행히도 CanCompareBits()
와 FastEquals()
(정적 메서드 모두) extern ( [MethodImpl(MethodImplOptions.InternalCall)]
)이며 사용할 수있는 소스가 없습니다.
한 사례를 비트별로 비교할 수있는 이유와 다른 사례를 비교할 수없는 이유로 돌아 가기 (정렬 문제 일 수 있습니까?)
답변
그것은 않습니다 모노의 gmcs 2.4.2.3으로, 나를 위해 진정한 제공합니다.
답변
간단한 테스트 사례 :
Console.WriteLine("Good: " + new Good().Equals(new Good { d = -.0 }));
Console.WriteLine("Bad: " + new Bad().Equals(new Bad { d = -.0 }));
public struct Good {
public double d;
public int f;
}
public struct Bad {
public double d;
}
편집 : 버그도 float와 함께 발생하지만 구조체의 필드가 8 바이트의 배수가 될 때만 발생합니다.
답변
신호 비트 만 0.0
다르므로 비트 단위 비교와 관련이 있어야합니다 -0.0
.