본문 바로가기

Programming/C#

Using custom class as C# Dictionary key type

반응형

 

요약 : 직접 만든 클래스를 Dictionary의 key로 사용하고 싶을 경우 추가적으로 해야 할 작업을 알아본다.

 


1. 아래와 같이 Point class를 만들고 좌표가 같을 경우 같은 Point로 인식하게 하고 싶다.

public class Point
{
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public int X { get; set; }
    public int Y { get; set; }
}

 

현재 상태에서 좌표가 같은 두 Point를 Dictionary에 Add()하면 예외 없이 정상적으로 추가된다.

var p1 = new Point(0, 0);
var p2 = new Point(0, 0);

var points = new Dictionary<Point, int>();

points.Add(p1, 0);
points.Add(p2, 1);

 

 

3. 좌표가 같은 Point를 동일한 Point로 인식하게 하기 위해 Dictionary의 Add()에서 key를 어떤 식으로 체크하는지 살펴보았다.

Add()는 내부적으로 Insert()를 호출하고

public void Add(TKey key, TValue value) {
    Insert(key, value, true);
}

 

Insert()는 해시값과 Equals()를 이용해 모두 true일 경우 중복 키 삽입으로 판단하고 있다.

private void Insert(TKey key, TValue value, bool add) {
   ...
   
   int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
   
   ...
   
   if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) {
       if (add) { 
           ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);
       }
   ...
   

 

ref : github.com/microsoft/referencesource/blob/master/mscorlib/system/collections/generic/dictionary.cs

 

4. 다시 말해 좌표가 같을 경우 같은 Point로 인식하게 하려면 Equals()와 GetHashCode()를 x, y를 사용해 적절히 override 해주면 된다.

public class Point
{
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public int X { get; set; }
    public int Y { get; set; }

    private bool Equals(Point point)
    {
      	return point.X.Equals(X) && point.Y.Equals(Y);
    }

    public override bool Equals(object obj)
    {
      	return this.Equals(obj as Point);
    }

    public override int GetHashCode()
    {
      	return HashCode.Combine(X, Y);
    }
}

 

5. override후 좌표가 같은 Point 추가 시 System.ArgumentException: An item with the same key has already been added 예외가 발생하는 것을 볼 수 있다.

var p1 = new Point(0, 0);
var p2 = new Point(0, 0);

var points = new Dictionary<Point, int>();

Assert.ThrowsException<ArgumentException>(() => 
{
    points.Add(p1, 0);
    points.Add(p2, 0);
});
반응형