Wednesday, May 22, 2013

HashSet

HashSet<T>은 C#에서 Set 대용으로(ie, 중복을 허용하지 않는 컬렉션) 흔히 쓰는데, equality를  hash code를 이용해서 검증하기 때문에 Object.GetHashCode()를 오버라이딩해 주어야 한다.

문제는 어느 인터페이스를 구현할 것인가이다. 구글에서 대충 검색하다 보면 IEqualityComparer<T>와 IEquatable<T>이 나타날 텐데, 감으로는 IEquatable<T>인 것 같지만(맞음) 과연 IEqualityComparer<T>는 무엇인가 하는 궁금증이 생긴다.
   (사실 이 이유만은 아니다. Visual Studio에서 IEquatable의 정의를 따라가 보면,
          bool Equals(T other)
   만 있고 GetHashCode가 없어서 IEquatable<T> 구현 없이
   그냥 Equals와 GetHashCode 를 override하고 말겠다는 생각이 든다...*)

어찌됐든, 두 인터페이스 중 어느 것을 써야 하는가에 대해서는, 아래 문서가 이를 잘 설명하고 있는데, 특히 마지막 답변이 이해하기 쉬운 예제와 함께 잘 설명하고 있다.

http://stackoverflow.com/questions/9316918/what-is-the-difference-between-iequalitycomparert-and-iequatablet

1. class T : IEquatable<T> 형태로 쓴다.
 * class MyClass : IEquatable<T> 형태는 (대개) 의미가 없다.
 * public bool Equals(T other)를 구현하면 된다.
   - Object.Equals(Object o)에 비해서 parameter가 T가 되고(캐스팅이나 as가 필요 없어짐#),
    override 키워드를 안 써도 경고문이 뜨지 않는 장점이 있다. <주>

 * ==, !=, GetHashCode와 nongeneric Equals도 오버라이딩해야 한다.
   - ...라고는 하는데 GetHashCode()만 해도 HashSet을 쓰는 데는 별 문제가 없다(아직까지는...).

2. IEqualityComparer는 특수한 상황에서만 쓰이는 동일성 비교 policy를 만들 때 쓴다. 예를 들어 특정 '조건을 만족하면 같은 것으로 치는' 상황이다. HashSet을 예로 들면, 평소에는 유일성이 인정되는 객체는 무조건 다 받아주다가(Add), 어떤 상황이 되면, 특정 조건을 만족하는 객체는 무조건 받지 않는 것이다.
class Person { 
    public int Age; 
} // Equals를 재정의하지 않았으므로 ReferenceEquals가 호출될 것이다.
class AgeEqualityTester : IEqualityComparer<Person> {
    public bool Equals(Person x, Person y) { return (x.Age==y.Age); }
    public int GetHashCode(Person p) { return p.Age; }
}
var people = new Person[] {new Person {age=23} };
var person = new Person() { age=23 };

var b1 = people.Contains(person); //FALSE;
var b2 = people.Contains(person, new AgeEqualityTester()); //TRUE

여기서 ICollection.Contains() 메서드의 새로운 사용법을 배울 수 있다 :)

Person/People 예는 이해하기는 쉬운데 별로 재미는 없다. 이런 걸 생각해 보자:
복잡한 weighted graph data structure가 있다. 이것의 개략적인 모양을 2차원 화면에 그리려 하는데, 모든 edge를 나타내기는 어려워서, weight만 다르고 시작점과 끝점이 같은 edge가 여러 개 있을 때 그들 중 하나만 그리기로 하였다. --> 한 번 그린 edge를 set에 넣고 두 번째부터 안 그리든지, 그릴 set을 처음에 만들고 시작하든지 알아서...

그럼에도 모르겠는 거: IEquatable에 왜 GetHashCode()가 포함되어 있지 않은가?




*또한 IEquatable에 없는 GetHashCode 멤버 함수가 IEqualityComparer에는 있다! -_-
그런데 IEqualityComparer를 구현해서 HashSet에서의 동일성 검증에 사용하려 하면, 그렇게 되지도 않는다. 우선 Equals의 원형부터
    public bool Equals(object objA, object objB)
로서, 마치 static 함수 같은 모양을 하고 있다(그러나 static은 아니다). C++/STL에서 operator overloading 하던 때의 짜증이 떠오른다... 이는 equality comparer라는 이름이 이미 암시하고 있기는 하다. 비교 대상이 아니라 '두 대상을 비교해 주는 무언가'라는 것.

# as는 이렇게 쓴다. 캐스트와 비슷하나, 변환이 실패했을 때 exception을 발생시키지 않고 그냥 null을 리턴한다.
class MyType {int m1; int m2;} // 모두 public 가정
public bool Equals(Object obj) {
    var p = obj as MyType; // 캐스트와 비슷
    if(p==null) return false;
    return m1==p.m1 && m2==p.m2; // custom equality test 예제
}

No comments:

Post a Comment

창 핸들을 만드는 동안 오류가 발생했습니다

System.ComponentModel.Win32Exception was unhandled   MyForm w = new MyForm IntPtr handle = wnd.Handle;   // Exception occurs here class MyFo...