객체의 필드를 일반 사전 키로 사용


120

객체를의 키로 사용하려면 Dictionary특정 방식으로 비교하기 위해 어떤 메서드를 재정의해야합니까?

속성이있는 클래스가 있다고 가정합니다.

class Foo {
    public string Name { get; set; }
    public int FooID { get; set; }

    // elided
} 

그리고 다음을 만들고 싶습니다.

Dictionary<Foo, List<Stuff>>

Foo동일한 개체를 FooID동일한 그룹으로 간주하고 싶습니다 . Foo클래스 에서 어떤 메서드를 재정의해야 합니까?

요약하면, 저는 Stuff개체를 Foo개체 별로 그룹화 된 목록 으로 분류하고 싶습니다 . Stuff개체는 FooID카테고리에 연결하는 데 사용됩니다.

답변:


151

기본적으로 두 가지 중요한 방법은 GetHashCode()Equals()입니다. 두 항목이 같으면 ( Equals()true를 반환) 동일한 해시 코드를 갖는 것이 중요합니다 . 예를 들어, "return FooID;" 는 AS GetHashCode()는 경기와 같은 것을합니다. 을 구현할 수도 IEquatable<Foo>있지만 이는 선택 사항입니다.

class Foo : IEquatable<Foo> {
    public string Name { get; set;}
    public int FooID {get; set;}

    public override int GetHashCode() {
        return FooID;
    }
    public override bool Equals(object obj) {
        return Equals(obj as Foo);
    }
    public bool Equals(Foo obj) {
        return obj != null && obj.FooID == this.FooID;
    }
}

마지막으로, 또 다른 대안은 IEqualityComparer<T>동일한 작업을 수행하는 것입니다.


5
+1하고이 스레드를 탈취하려는 것은 아니지만 GetHashCode ()가 FooId.GetHashCode ()를 반환해야한다는 인상을 받았습니다. 이것이 올바른 패턴이 아닙니까?
Ken Browning

8
@Ken-음, 필요한 기능을 제공하는 int를 반환하면됩니다. 어떤 FooID가 FooID.GetHashCode ()와 마찬가지로 수행 할 것입니다. 구현 세부 사항으로 Int32.GetHashCode ()는 "return this;"입니다. 다른 유형 (문자열 등)의 경우 예 : .GetHashCode ()가 매우 유용합니다.
Marc Gravell

2
감사! 재정의 된 메서드가 필요한 Dicarionary에만 해당했기 때문에 IEqualityComparer <T>를 사용했습니다.
Dana

1
해시 테이블 (Dictionary <T>, Dictionary, HashTable 등)을 기반으로하는 컨테이너의 성능은 사용 된 해시 함수의 품질에 따라 달라진다는 점에 유의해야합니다. 단순히 FooID를 해시 코드로 사용하면 컨테이너 성능이 매우 저하 될 수 있습니다.
Jørgen Fogh 2014 년

2
@ JørgenFogh 나는 그것을 아주 잘 알고 있습니다; 제시된 예는 명시된 의도와 일치합니다. 해시 불변성과 관련된 많은 관련 문제가 있습니다. id는 이름보다 자주 변경되지 않으며 일반적으로 안정적으로 고유하고 동등한 지표입니다. 그러나 사소한 주제는 아닙니다.
Marc Gravell

33

FooID그룹의 식별자로 사용하려면 Foo 객체 대신 사전의 키로 사용해야합니다.

Dictionary<int, List<Stuff>>

Foo객체를 키로 사용 하려면 속성 만 고려하도록 GetHashCodeand Equals메서드를 구현 하면 FooID됩니다. Name속성은 바로이 지금까지처럼 죽은 무게 될 것 Dictionary그냥 사용합니다, 그래서 걱정했다 Foo의 래퍼로서int .

따라서 FooID값을 직접 사용하는 것이 더 낫습니다. 그러면에서 Dictionary이미 지원하므로 아무것도 구현할 필요가 없습니다 .int 키로 .

편집 :
당신이 사용하려는 경우 Foo키 어쨌든 같은 클래스의는 IEqualityComparer<Foo>쉽게 구현할 수 :

public class FooEqualityComparer : IEqualityComparer<Foo> {
   public int GetHashCode(Foo foo) { return foo.FooID.GetHashCode(); }
   public bool Equals(Foo foo1, Foo foo2) { return foo1.FooID == foo2.FooID; }
}

용법:

Dictionary<Foo, List<Stuff>> dict = new Dictionary<Foo, List<Stuff>>(new FooEqualityComparer());

1
더 정확하게는 int는 키로 사용하는 데 필요한 메서드 / 인터페이스를 이미 지원합니다. 사전에는 int 또는 다른 유형에 대한 직접적인 지식이 없습니다.
Jim Mischel

나는 그것에 대해 생각했지만 다양한 이유로 객체를 사전 키로 사용하는 것이 더 깨끗하고 편리했습니다.
Dana

1
글쎄, 당신은 실제로 id를 키로 사용하고 있기 때문에 객체를 키로 사용하는 것처럼 보입니다.
Guffa

8

Foo의 경우 object.GetHashCode () 및 object.Equals ()를 재정의해야합니다.

사전은 GetHashCode ()를 호출하여 각 값에 대한 해시 버킷을 계산하고 Equals를 호출하여 두 Foo가 동일한 지 여부를 비교합니다.

좋은 해시 코드를 계산해야하지만 (동일한 해시 코드를 가진 동일한 Foo 객체를 피하십시오) 두 개의 동일한 Foos가 동일한 해시 코드를 갖는지 확인하십시오. Equals-Method로 시작한 다음 (GetHashCode ()에서) xor Equals에서 비교하는 모든 멤버의 해시 코드를 사용할 수 있습니다.

public class Foo { 
     public string A;
     public string B;

     override bool Equals(object other) {
          var otherFoo = other as Foo;
          if (otherFoo == null)
             return false;
          return A==otherFoo.A && B ==otherFoo.B;
     }

     override int GetHashCode() {
          return 17 * A.GetHashCode() + B.GetHashCode();
     }
}

2
제쳐두고-그러나 xor (^)는 종종 많은 대각선 충돌 (예 : { "foo", "bar"} 대 { "bar", "foo"})을 발생시키기 때문에 해시 코드에 대해 좋지 않은 결합자를 만듭니다. 선택은 각 항을 곱하고 더하는 것입니다. 예 : 17 * a.GetHashCode () + B.GetHashCode ();
Marc Gravell

2
마크, 무슨 말인지 알겠습니다. 하지만 마법의 숫자 17은 어떻게 얻습니까? 해시 결합을위한 곱셈기로 소수를 사용하는 것이 유리합니까? 그렇다면 그 이유는 무엇입니까?
froh42

다음을 반환하는 것이 좋습니다 : (A + B) .GetHashCode () 대신 : 17 * A.GetHashCode () + B.GetHashCode () 이것은 다음과 같습니다 : 1) 충돌 가능성이 적고 2) 정수 오버 플로우.
Charles Burns

(A + B) .GetHashCode ()는 (A, B)의 다른 세트가 동일한 문자열에 연결되면 동일한 해시를 생성 할 수 있으므로 매우 나쁜 해싱 알고리즘을 만듭니다. "hellow"+ "ned"는 "hell"+ "owned"와 동일하며 동일한 해시를 생성합니다.
kaesve

@kaesve (A + ""+ B) .GetHashCode ()는 어떻습니까?
Timeless

1

Hashtable수업 은 어떻습니까 !

Hashtable oMyDic = new Hashtable();
Object oAnyKeyObject = null;
Object oAnyValueObject = null;
oMyDic.Add(oAnyKeyObject, oAnyValueObject);
foreach (DictionaryEntry de in oMyDic)
{
   // Do your job
}

위의 방법으로 모든 객체 (클래스 객체)를 일반 사전 키로 사용할 수 있습니다. :)


1

나는 같은 문제가 있었다. 이제 Equals 및 GetHashCode 재정의로 인해 키로 시도한 모든 개체를 사용할 수 있습니다.

다음은 Equals (object obj) 및 GetHashCode ()의 재정의 내부에서 사용할 메서드로 빌드 한 클래스입니다. 저는 제네릭과 대부분의 객체를 처리 할 수있는 해싱 알고리즘을 사용하기로 결정했습니다. 여기에 일부 유형의 개체에 대해 작동하지 않는 항목이 있고 개선 할 방법이 있으면 알려주세요.

public class Equality<T>
{
    public int GetHashCode(T classInstance)
    {
        List<FieldInfo> fields = GetFields();

        unchecked
        {
            int hash = 17;

            foreach (FieldInfo field in fields)
            {
                hash = hash * 397 + field.GetValue(classInstance).GetHashCode();
            }
            return hash;
        }
    }

    public bool Equals(T classInstance, object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }
        if (ReferenceEquals(this, obj))
        {
            return true;
        }
        if (classInstance.GetType() != obj.GetType())
        {
            return false;
        }

        return Equals(classInstance, (T)obj);
    }

    private bool Equals(T classInstance, T otherInstance)
    {
        List<FieldInfo> fields = GetFields();

        foreach (var field in fields)
        {
            if (!field.GetValue(classInstance).Equals(field.GetValue(otherInstance)))
            {
                return false;
            }
        }

        return true;
    }

    private List<FieldInfo> GetFields()
    {
        Type myType = typeof(T);

        List<FieldInfo> fields = myType.GetTypeInfo().DeclaredFields.ToList();
        return fields;
    }
}

다음은 수업에서 사용되는 방법입니다.

public override bool Equals(object obj)
    {
        return new Equality<ClassName>().Equals(this, obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return new Equality<ClassName>().GetHashCode(this);
        }
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.