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


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.