Object.GetHashCode ()의 기본 구현
기본 구현은 어떻게 GetHashCode()
작동합니까? 그리고 구조, 클래스, 배열 등을 효율적이고 충분히 처리합니까?
나는 어떤 경우에 내 자신을 포장해야하며 어떤 경우에는 기본 구현에 안전하게 의존하여 잘 수행 할 수 있는지 결정하려고합니다. 가능하다면 바퀴를 재발 명하고 싶지 않습니다.
namespace System {
public class Object {
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int InternalGetHashCode(object obj);
public virtual int GetHashCode() {
return InternalGetHashCode(this);
}
}
}
InternalGetHashCode 는 CLR 의 ObjectNative :: GetHashCode 함수에 맵핑 되며 다음과 같습니다.
FCIMPL1(INT32, ObjectNative::GetHashCode, Object* obj) {
CONTRACTL
{
THROWS;
DISABLED(GC_NOTRIGGER);
INJECT_FAULT(FCThrow(kOutOfMemoryException););
MODE_COOPERATIVE;
SO_TOLERANT;
}
CONTRACTL_END;
VALIDATEOBJECTREF(obj);
DWORD idx = 0;
if (obj == 0)
return 0;
OBJECTREF objRef(obj);
HELPER_METHOD_FRAME_BEGIN_RET_1(objRef); // Set up a frame
idx = GetHashCodeEx(OBJECTREFToObject(objRef));
HELPER_METHOD_FRAME_END();
return idx;
}
FCIMPLEND
GetHashCodeEx 의 전체 구현 은 상당히 크기 때문에 C ++ 소스 코드에 연결하는 것이 더 쉽습니다 .
클래스의 경우 기본값은 기본적으로 참조 평등이며 일반적으로 좋습니다. 구조체를 작성하는 경우 평등을 무시하는 것이 더 일반적이지만 (권투를 피하기 위해), 어쨌든 구조체를 작성하는 것은 매우 드!니다!
평등을 무시하면, 당신은 항상 일치해야 Equals()
하고을 GetHashCode()
(경우 즉, 두 개의 값, Equals()
true를 돌려줍니다 그들이 있어야 같은 해시 코드를 반환하지만, 그 반대가되어 있지 필수) - 그리고 또한 제공하는 것이 일반적입니다 ==
/ !=
연산자를 종종에 IEquatable<T>
너무 구현하십시오 .
해시 코드를 생성하는 경우 팩터링 합계를 사용하는 것이 일반적입니다. 예를 들어 기본 2 필드 해시와 같이 쌍을 이루는 값에서 충돌을 피할 수 있습니다.
unchecked // disable overflow, for the unlikely possibility that you
{ // are compiling with overflow-checking enabled
int hash = 27;
hash = (13 * hash) + field1.GetHashCode();
hash = (13 * hash) + field2.GetHashCode();
return hash;
}
이것은 다음과 같은 이점이 있습니다.
- {1,2}의 해시는 {2,1}의 해시와 동일하지 않습니다
- the hash of {1,1} is not the same as the hash of {2,2}
etc - which can be common if just using an unweighted sum, or xor (^
), etc.
The documentation for the GetHashCode
method for Object says "the default implementation of this method must not be used as a unique object identifier for hashing purposes." and the one for ValueType says "If you call the derived type's GetHashCode method, the return value is not likely to be suitable for use as a key in a hash table.".
The basic data types like byte
, short
, int
, long
, char
and string
implement a good GetHashCode method. Some other classes and structures, like Point
for example, implement a GetHashCode
method that may or may not be suitable for your specific needs. You just have to try it out to see if it's good enough.
The documentation for each class or structure can tell you if it overrides the default implementation or not. If it doesn't override it you should use your own implementation. For any classes or structs that you create yourself where you need to use the GetHashCode
method, you should make your own implementation that uses the appropriate members to calculate the hash code.
Since I couldn't find an answer that explains why we should override GetHashCode
and Equals
for custom structs and why the default implementation "is not likely to be suitable for use as a key in a hash table", I'll leave a link to this blog post, which explains why with a real-case example of a problem that happened.
I recommend reading the whole post, but here is a summary (emphasis and clarifications added).
Reason the default hash for structs is slow and not very good:
The way the CLR is designed, every call to a member defined in
System.ValueType
orSystem.Enum
types [may] cause a boxing allocation [...]An implementer of a hash function faces a dilemma: make a good distribution of the hash function or to make it fast. In some cases, it's possible to achieve them both, but it is hard to do this generically in
ValueType.GetHashCode
.The canonical hash function of a struct "combines" hash codes of all the fields. But the only way to get a hash code of a field in a
ValueType
method is to use reflection. So, the CLR authors decided to trade speed over the distribution and the defaultGetHashCode
version just returns a hash code of a first non-null field and "munges" it with a type id [...] This is a reasonable behavior unless it's not. For instance, if you're unlucky enough and the first field of your struct has the same value for most instances, then a hash function will provide the same result all the time. And, as you may imagine, this will cause a drastic performance impact if these instances are stored in a hash set or a hash table.[...] Reflection-based implementation is slow. Very slow.
[...] Both
ValueType.Equals
andValueType.GetHashCode
have a special optimization. If a type does not have "pointers" and is properly packed [...] then more optimal versions are used:GetHashCode
iterates over an instance and XORs blocks of 4 bytes andEquals
method compares two instances usingmemcmp
. [...] But the optimization is very tricky. First, it is hard to know when the optimization is enabled [...] Second, a memory comparison will not necessarily give you the right results. Here is a simple example: [...]-0.0
and+0.0
are equal but have different binary representations.
Real-world issue described in the post:
private readonly HashSet<(ErrorLocation, int)> _locationsWithHitCount;
readonly struct ErrorLocation
{
// Empty almost all the time
public string OptionalDescription { get; }
public string Path { get; }
public int Position { get; }
}
We used a tuple that contained a custom struct with default equality implementation. And unfortunately, the struct had an optional first field that was almost always equals to [empty string]. The performance was OK until the number of elements in the set increased significantly causing a real performance issue, taking minutes to initialize a collection with tens of thousands of items.
So, to answer the question "in what cases I should pack my own and in what cases I can safely rely on the default implementation", at least in the case of structs, you should override Equals
and GetHashCode
whenever your custom struct might be used as a key in a hash table or Dictionary
.
I would also recommend implementing IEquatable<T>
in this case, to avoid boxing.
As the other answers said, if you're writing a class, the default hash using reference equality is usually fine, so I wouldn't bother in this case, unless you need to override Equals
(then you would have to override GetHashCode
accordingly).
Generally speaking, if you're overriding Equals, you want to override GetHashCode. The reason for this is because both are used to compare equality of your class/struct.
Equals is used when checking Foo A, B;
if (A == B)
Since we know the pointer isn't likely to match, we can compare the internal members.
Equals(obj o)
{
if (o == null) return false;
MyType Foo = o as MyType;
if (Foo == null) return false;
if (Foo.Prop1 != this.Prop1) return false;
return Foo.Prop2 == this.Prop2;
}
GetHashCode is generally used by hash tables. The hashcode generated by your class should always be the same for a classes give state.
I typically do,
GetHashCode()
{
int HashCode = this.GetType().ToString().GetHashCode();
HashCode ^= this.Prop1.GetHashCode();
etc.
return HashCode;
}
Some will say that the hashcode should only be calculated once per object lifetime, but I don't agree with that (and I'm probably wrong).
Using the default implementation provided by object, unless you have the same reference to one of your classes, they will not be equal to each other. By overriding Equals and GetHashCode, you can report equality based on internal values rather than the objects reference.
If you're just dealing with POCOs you can use this utility to simplify your life somewhat:
var hash = HashCodeUtil.GetHashCode(
poco.Field1,
poco.Field2,
...,
poco.FieldN);
...
public static class HashCodeUtil
{
public static int GetHashCode(params object[] objects)
{
int hash = 13;
foreach (var obj in objects)
{
hash = (hash * 7) + (!ReferenceEquals(null, obj) ? obj.GetHashCode() : 0);
}
return hash;
}
}
참고URL : https://stackoverflow.com/questions/720177/default-implementation-for-object-gethashcode
'Programing' 카테고리의 다른 글
css 파일을 안에 넣는 경우 차이점은 무엇입니까? (0) | 2020.06.09 |
---|---|
.NET에서 바이트의 리터럴 접미사? (0) | 2020.06.09 |
"Temporary ASP.NET Files"폴더는 무엇입니까? (0) | 2020.06.09 |
Objective C ++ 란 무엇입니까? (0) | 2020.06.09 |
Windows EXE 파일 서명 (0) | 2020.06.09 |