문제

이 질문은 에 대한 토론에서 나왔습니다. 튜플.

나는 튜플이 가져야 할 해시 코드에 대해 생각하기 시작했습니다.KeyValuePair 클래스를 튜플로 허용하면 어떻게 되나요?GetHashCode() 메서드를 재정의하지 않으므로 아마도 "자식"의 해시 코드를 인식하지 못할 것입니다...따라서 런타임에서는 실제 개체 구조를 인식하지 못하는 Object.GetHashCode()를 호출합니다.

그런 다음 오버로드된 GetHashCode() 및 Equals()로 인해 실제로 Equal인 일부 참조 유형의 두 인스턴스를 만들 수 있습니다.그리고 사전을 "속이기" 위해 튜플의 "하위"로 사용합니다.

하지만 작동하지 않습니다!런타임은 어떻게든 튜플의 구조를 파악하고 클래스의 오버로드된 GetHashCode를 호출합니다!

어떻게 작동하나요?Object.GetHashCode()의 분석은 무엇입니까?

복잡한 키를 사용할 때 일부 나쁜 시나리오에서 성능에 영향을 미칠 수 있습니까?(아마도 불가능한 시나리오...하지만 그래도)

다음 코드를 예로 들어보세요.

namespace csharp_tricks
{
    class Program
    {
        class MyClass
        {
            int keyValue;
            int someInfo;

            public MyClass(int key, int info)
            {
                keyValue = key;
                someInfo = info;
            }

            public override bool Equals(object obj)
            {
                MyClass other = obj as MyClass;
                if (other == null) return false;

                return keyValue.Equals(other.keyValue);
            }

            public override int GetHashCode()
            {
                return keyValue.GetHashCode();
            }
        }

        static void Main(string[] args)
        {
            Dictionary<object, object> dict = new Dictionary<object, object>();

            dict.Add(new KeyValuePair<MyClass,object>(new MyClass(1, 1), 1), 1);

            //here we get the exception -- an item with the same key was already added
            //but how did it figure out the hash code?
            dict.Add(new KeyValuePair<MyClass,object>(new MyClass(1, 2), 1), 1); 

            return;
        }
    }
}

업데이트 아래 답변에서 이에 대한 설명을 찾은 것 같습니다.그 주요 결과는 다음과 같습니다.

  • 키와 해시 코드에 주의하세요 :-)
  • 복잡한 사전 키의 경우 Equals() 및 GetHashCode()를 올바르게 재정의해야 합니다.
도움이 되었습니까?

해결책 4

이제 힌트를 얻은 것 같습니다.

KeyValuePair가 참조 유형인 줄 알았는데 그렇지 않고 구조체입니다.그래서 ValueType.GetHashCode() 메서드를 사용합니다.MSDN에서는 다음과 같이 말합니다."파생 유형의 하나 이상의 필드가 반환 값을 계산하는 데 사용됩니다."

실제 참조 유형을 "튜플 제공자"로 사용하면 사전(또는 자신...)을 속이게 됩니다.

using System.Collections.Generic;

namespace csharp_tricks
{
    class Program
    {
        class MyClass
        {
            int keyValue;
            int someInfo;

            public MyClass(int key, int info)
            {
                keyValue = key;
                someInfo = info;
            }

            public override bool Equals(object obj)
            {
                MyClass other = obj as MyClass;
                if (other == null) return false;

                return keyValue.Equals(other.keyValue);
            }

            public override int GetHashCode()
            {
                return keyValue.GetHashCode();
            }
        }

        class Pair<T, R>
        {
            public T First { get; set; }
            public R Second { get; set; }
        }

        static void Main(string[] args)
        {
            var dict = new Dictionary<Pair<int, MyClass>, object>();

            dict.Add(new Pair<int, MyClass>() { First = 1, Second = new MyClass(1, 2) }, 1);

            //this is a pair of the same values as previous! but... no exception this time...
            dict.Add(new Pair<int, MyClass>() { First = 1, Second = new MyClass(1, 3) }, 1);

            return;
        }
    }
}

다른 팁

변경 가능한 클래스에서 GetHashcode() 및 Equals()를 재정의하지 말고 변경 불가능한 클래스나 구조에서만 재정의하십시오. 그렇지 않으면 키로 사용된 개체를 수정하면 해시 테이블이 더 이상 제대로 작동하지 않습니다. 키 객체가 수정된 후 키와 연관된 값을 검색합니다.)

또한 해시 테이블은 키 개체 자체를 식별자로 사용하는 개체를 식별하기 위해 해시 코드를 사용하지 않습니다. 해시 테이블에 항목을 추가하는 데 사용되는 모든 키가 서로 다른 해시 코드를 반환할 필요는 없지만 그렇게 하는 것이 좋습니다. 그렇지 않으면 성능이 떨어집니다. 큰 고통을 받습니다.

다음은 쿼드 튜플(내부에 4개의 튜플 구성 요소 포함)에 대한 적절한 해시 및 동등 구현입니다.이 코드는 HashSet 및 사전에서 이 특정 튜플의 적절한 사용을 보장합니다.

주제에 대한 추가 정보(소스 코드 포함) 여기.

메모 사용법 선택 해제됨 키워드(오버플로 방지) 및 obj가 null인 경우 NullReferenceException 발생(기본 메서드에서 요구하는 대로)

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj))
        throw new NullReferenceException("obj is null");
    if (ReferenceEquals(this, obj)) return true;
    if (obj.GetType() != typeof (Quad<T1, T2, T3, T4>)) return false;
    return Equals((Quad<T1, T2, T3, T4>) obj);
}

public bool Equals(Quad<T1, T2, T3, T4> obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    return Equals(obj.Item1, Item1)
        && Equals(obj.Item2, Item2)
            && Equals(obj.Item3, Item3)
                && Equals(obj.Item4, Item4);
}

public override int GetHashCode()
{
    unchecked
    {
        int result = Item1.GetHashCode();
        result = (result*397) ^ Item2.GetHashCode();
        result = (result*397) ^ Item3.GetHashCode();
        result = (result*397) ^ Item4.GetHashCode();
        return result;
    }
}
public static bool operator ==(Quad<T1, T2, T3, T4> left, Quad<T1, T2, T3, T4> right)
{
    return Equals(left, right);
}


public static bool operator !=(Quad<T1, T2, T3, T4> left, Quad<T1, T2, T3, T4> right)
{
    return !Equals(left, right);
}

이것을 확인해보세요 우편 object.GetHashCode 작동 방식에 대한 자세한 내용은 Brad Abrams의 글과 Brian Grunkemeyer의 의견을 참조하세요.그리고 아얀데님 블로그의 첫댓글도 구경해보세요 우편.프레임워크의 현재 릴리스가 여전히 이러한 규칙을 따르는지 아니면 Brad가 암시한 것처럼 실제로 변경했는지는 알 수 없습니다.

더 이상 책 참조가 없으며 확인하기 위해 찾아야 하지만 기본 기본 해시가 개체의 모든 구성원을 함께 해시한 줄 알았습니다.CLR의 작동 방식 때문에 액세스할 수 있었기 때문에 CLR만큼 작성할 수 있는 것이 아니었습니다.

그것은 내가 간략하게 읽은 내용을 완전히 기억하는 것이므로 원하는 대로 받아들이십시오.

편집하다: 그 책은 C# 내부 MS Press에서.덮개에 톱날이 있는 것.저자는 CLR에서 구현된 방식, 언어가 MSIL로 변환된 방식 등을 설명하는 데 많은 시간을 보냈습니다.등등.책을 찾아보면 나쁘지 않은 책이다.

편집하다: 다음과 같은 링크를 생성하세요.

Object.gethashCode ()는 System.Object 클래스의 내부 필드를 사용하여 해시 값을 생성합니다.생성 된 각 객체에는 생성 될 때 정수로 저장된 고유 한 개체 키가 할당됩니다.이 키는 1에서 시작하여 모든 유형의 새로운 객체가 생성 될 때마다 증가합니다.

흠 개체를 해시 키로 사용하려면 자체 해시 코드 몇 개를 작성해야 할 것 같습니다.

따라서 아마도 "자식"의 해시 코드를 인식하지 못할 것입니다.

귀하의 예는 그렇지 않은 것으로 보입니다 :-) 키의 해시 코드 MyClass 그리고 그 가치 1 둘 다 마찬가지야 KeyValuePair의 .KeyValuePair 구현은 두 가지 모두를 사용해야 합니다. Key 그리고 Value 자신의 해시 코드에 대해

위로 이동하면 사전 클래스는 고유 키를 원합니다.각 키가 제공하는 해시코드를 사용하여 문제를 파악합니다.런타임이 호출되지 않는다는 점을 기억하세요. Object.GetHashCode(), 이지만, 사용자가 제공한 인스턴스에서 제공하는 GetHashCode() 구현을 호출하고 있습니다.

좀 더 복잡한 경우를 생각해 보세요.

public class HappyClass
{

    enum TheUnit
    {
        Points,
        Picas,
        Inches
    }

    class MyDistanceClass
    {
        int distance;
        TheUnit units;

        public MyDistanceClass(int theDistance, TheUnit unit)
        {
            distance = theDistance;

            units = unit;
        }
        public static int ConvertDistance(int oldDistance, TheUnit oldUnit, TheUnit newUnit)
        {
            // insert real unit conversion code here :-)
            return oldDistance * 100;
        }

        /// <summary>
        /// Figure out if we are equal distance, converting into the same units of measurement if we have to
        /// </summary>
        /// <param name="obj">the other guy</param>
        /// <returns>true if we are the same distance</returns>
        public override bool Equals(object obj)
        {
            MyDistanceClass other = obj as MyDistanceClass;
            if (other == null) return false;

            if (other.units != this.units)
            {
                int newDistance = MyDistanceClass.ConvertDistance(other.distance, other.units, this.units);
                return distance.Equals(newDistance);
            }
            else
            {
                return distance.Equals(other.distance);
            }


        }

        public override int GetHashCode()
        {
            // even if the distance is equal in spite of the different units, the objects are not
            return distance.GetHashCode() * units.GetHashCode();
        }
    }
    static void Main(string[] args)
    {

        // these are the same distance... 72 points = 1 inch
        MyDistanceClass distPoint = new MyDistanceClass(72, TheUnit.Points);
        MyDistanceClass distInch = new MyDistanceClass(1, TheUnit.Inch);

        Debug.Assert(distPoint.Equals(distInch), "these should be true!");
        Debug.Assert(distPoint.GetHashCode() != distInch.GetHashCode(), "But yet they are fundimentally different values");

        Dictionary<object, object> dict = new Dictionary<object, object>();

        dict.Add(new KeyValuePair<MyDistanceClass, object>(distPoint, 1), 1);

        //this should not barf
        dict.Add(new KeyValuePair<MyDistanceClass, object>(distInch, 1), 1);

        return;
    }

}

원래...내 예의 경우 동일한 거리에 있는 두 개체가 Equals에 대해 "true"를 반환하지만 다른 해시 코드를 반환하기를 원할 것입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top