목록

Dictionary Key

— 문제발생 - Dictionary Key check(class 쓸경우)

처음에는 Dictionary Key로 사용할 struct로 정의해서 사용 했었다.

[Serializable]
public struct Index
{
    public int x;
    public int y;

    public Index(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

struct key는 dictionary key 비교 시 멤버변수 값 같으면 같은 key로 인식해서 아무생각 없이 사용했다.

Grid.Index _indexSameValue = new Grid.Index(i, j);
bool a = m_dicCellInfo.ContainsKey(_index);
bool b = m_dicCellInfo.ContainsKey(_indexSameValue);

Debug.Log("a : " + a);	// => true
Debug.Log("b : " + b);	// => true

Index 객체 내부에 변수 및 함수 추가될거 같아서 class로 변경

[Serializable]
public class Index
{
    public int x;
    public int y;

    public Index(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

내부 멤버변수 같아도 struct와 다르게 다른 key값으로 인식

Grid.Index _indexSameValue = new Grid.Index(i, j);
bool a = m_dicCellInfo.ContainsKey(_index);
bool b = m_dicCellInfo.ContainsKey(_indexSameValue);

Debug.Log("a : " + a);	// => true
Debug.Log("b : " + b);	// => false

— 추측 - Dictionary 내부 key값 비교 시 해당 객체의 operator 연산자를 통해 비교하는거 같다

Index class에 ==, != operator연산자를 정의 해줘보자.
(멤버변수 x,y를 vector2Int로 바꿈)

[Serializable]
public class Index
{
	public Vector2Int value;

	public Index(int x, int y)
	{
		value = new Vector2Int(x, y);
	}
    	public static bool operator ==(Index index1_, Index index2_)
    	{
    		return (index1_.value == index2_.value) ? true : false;
    	}
    	public static bool operator !=(Index index1_, Index index2_)
    	{
    		return (index1_.value != index2_.value) ? true : false;
    	}
}

=> 결과는 변한게 없었다. 내부적으로 내가 모르는 class 값 비교가 있는거 같다.

Grid.Index _indexSameValue = new Grid.Index(i, j);
bool a = m_dicCellInfo.ContainsKey(_index);
bool b = m_dicCellInfo.ContainsKey(_indexSameValue);

Debug.Log("a : " + a);	// => true
Debug.Log("b : " + b);	// => false

—해결 - ValueType.Equals(Object), GetHashCode()

Dictionary는 EqualityComparer로 Key값을 비교한다.

특별히 명시하지 않는 이상 Dictionary는 생성 될 떄 비교를 EqualityComparer.Default를 사용.

클래스같은 reference형식의 경우는 Object로 부터 상속받았기 떄문에, 특별히 명시 하지 않는 이상 기본적으로 비교 시 Object.Equals, Object.GetHashCode를 사용하므로 같은 형식의 클래스라도 개별적으로 고유 참조값이 다르므로 우리가 원하는 비교가 되지 않는다.

구조체는 System.ValueType으로 부터 상속 받으므로(기본적으로), 기본적으로 비교 시 ValueType.Equals를 사용하며 값 타입이라 같은 형식의 모든 구조체는 GetHashCode가 같은듯?(이부분은 추측).

=> 결론은 class는 Equals과 GetHashCode를 override 해줘야한다.

[Serializable]
public class Index
{
	public Vector2Int value;

	public Index(int x, int y)
	{
		value = new Vector2Int(x, y);
	}

	public override bool Equals(object obj_)
	{
		if (obj_ == null)
		return false;

		Index _index = obj_ as Index;
		return value.Equals(_index.value);
	}

	public override int GetHashCode()
	{
		return EqualityComparer<Vector2Int>.Default.GetHashCode(value);
	}

	public static bool operator ==(Index index1_, Index index2_)
	{
		return (index1_.value == index2_.value) ? true : false;
	}

	public static bool operator !=(Index index1_, Index index2_)
	{
		return (index1_.value != index2_.value) ? true : false;
	}
}

근데 해당 프로젝트에선 멤버변수 vector2만 필요할거 같아서 그냥 struct로 사용할듯…8-o


—추가작성 - 2019-10

기본기 까먹어서.. 다시보니

Dictionary -> 해시테이블의 종류 -> HashFunction통해 키값생성 -> object의 GetHashcode()이용

몇가지 새롭게 알아낸 사실들이 있어서 업데이트 겸 추가된 사항작성

Dictionary에서 containKey사용할 때 Dictionary에서 []통해 직접 가져올 때 GetHashCode, Equals함수를 통해 비교한다.

우선 두가지 방법으로 Key값 비교하도록 커스텀 Equals을 작성 할 수 있는데

첫번째 방법은 위에서 처럼 key값으로 지정할 클래스자체에 Equals를 override해서 작성하여도 되고,

두번째 방법은 IEqualityComparer인터페이스를 상속받아 Equals, GetHashCode를 오버라이딩받아 사용

Dictionary생성자 인자 값에 IEqualityComparer을 받는데 이걸 우리가 정의해준 클래스로 넣게 되면

내부적으로 비교연산지 우리가 만든 IEqualityComparer사용한다는 의미(설정안해주면 Default겠쥬??)

public class IndexEqualityComparer : IEqualityComparer<Index>
{
    public bool Equals(Index index1_, Index index2_)
    {
        if (index1_ == null && index2_ == null)
            return true;
        else if (index1_ == null || index2_ == null)
            return false;
        
        return (index1_.value == index2_.value);
    }

    public int GetHashCode(Index index_)
    {
        return index_.GetHashCode();
    }
}

이런식으로 넣어주면 Dictionary의 함수인 containKey등 호출 할 때 Index클래스 자체 Equals, GetHashCode를 호출해 비교하지 않고

IndexEqualityComparer통해 해당 Key값을 비교연산한다.

Dictionary<Index, int> _test = new Dictionary<Index, int>(new IndexEqualityComparer());

프로그래머 입맛에 따라 방법채택해 사용하면 될거같다. -> 해당 Dictionary컨테이너에만 비교 다르게 하고 싶을 때 사용하면 괜춘할듯


참고.

https://stackoverflow.com/questions/16472159/using-a-class-versus-struct-as-a-dictionary-key

https://ejonghyuck.github.io/blog/2016-12-12/dictionary-struct-enum-key/