Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 6개 있습니다.)
.NET Framework: 179. Dictionary.Get(A) 대신 Dictionary.Get(A.GetHashCode())를 사용해서는 안 되는 이유
; https://www.sysnet.pe.kr/2/0/889

.NET Framework: 295. 괜찮은 문자열 해시 함수?
; https://www.sysnet.pe.kr/2/0/1222

.NET Framework: 296. 괜찮은 문자열 해시함수? - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/1223

개발 환경 구성: 478. 파일의 (sha256 등의) 해시 값(checksum) 확인하는 방법
; https://www.sysnet.pe.kr/2/0/12176

.NET Framework: 1070. 사용자 정의 GetHashCode 메서드 구현은 C# 9.0의 record 또는 리팩터링에 맡기세요.
; https://www.sysnet.pe.kr/2/0/12669

.NET Framework: 2047. Golang, Python, C#에서의 CRC32 사용
; https://www.sysnet.pe.kr/2/0/13124




사용자 정의 GetHashCode 메서드 구현은 C# 9.0의 record 또는 리팩터링에 맡기세요.

GetHashCode 코드 질문이 종종 올라오니,

GethashCode와 String대한 질문
; https://www.sysnet.pe.kr/3/0/5514

GetHashCode 질문있습니다!
; https://www.sysnet.pe.kr/3/0/5480

간단하게 팁 정도로 공유해서 전달해 드리는 것이 좋을 듯해서 글을 써봅니다. ^^

보통, 닷넷에서 GetHashCode 메서드를 이용해 hash 값을 구하긴 해도 막상 우리가 만든 타입에서 GetHashCode를 작성하려고 하면 코드 구현에서 고민이 됩니다. 가령 다음과 같은 예제가 있을 때,

public class Person
{
    public string Name;
    public int Age;

    public override int GetHashCode()
    {
        // ... hashcode 계산 ...
    }
}

과연 저 값을 어떻게 계산해야 할지 고민이 될 것입니다. 이럴 때는, 그냥 마이크로소프트가 하는 방법을 따르는 것도 좋습니다. 이를 위해 동일한 타입을 C# 9.0의 record로,

C# 9.0 - (9) 레코드(Records)
; https://www.sysnet.pe.kr/2/0/12392

만들면,

public record Person2
{
    public string Name;
    public int Age;
}

빌드 결과물로부터 역어셈블러를 통해 다음의 결과를 얻을 수 있습니다.

public override int GetHashCode()
{
    return (EqualityComparer<Type>.Default.GetHashCode(this.EqualityContract) * -1521134295
     + EqualityComparer<string>.Default.GetHashCode(this.Name)) * -1521134295
     + EqualityComparer<int>.Default.GetHashCode(this.Age);
}

음... 별다른 양심의 거리낌 없이 ^^ 복사해서 쓰면 됩니다. 만약 컬렉션 내에 같은 타입끼리만 있다면 다음과 같은 식으로 간략화해 처리해도 무방합니다.

public class Person
{
    public string Name;
    public int Age;

    public override int GetHashCode()
    {
        return EqualityComparer<string>.Default.GetHashCode(this.Name)) * -1521134295
             + EqualityComparer<int>.Default.GetHashCode(this.Age);
    }
}

혹은 이렇게 단순화해도 좋을 듯 싶고.

public override int GetHashCode()
{
    return this.Name.GetHashCode() * -1521134295
            + this.Age.GetHashCode();
}




또는, Visual Studio를 사용하신다면 우 클릭을 해 "Quick Actions and Refactorings..." 메뉴를 불러,

cs_gethascode_1.png

선택하면 다음과 같이 "Generate Equals and GetHashCode..." 기능을 선택할 수 있습니다.

cs_gethascode_2.png

그럼 hash 값을 구할 멤버를 선택하는 대화창이 뜨고,

cs_gethascode_3.png

적절한 설정 후 "OK" 버튼을 누르면 다음과 같이 알아서 GetHashCode를 만들어 줍니다.

// .NET Core 프로젝트

public class Person
{
    public string Name;
    public int Age;

    public override bool Equals(object obj)
    {
        return obj is Person person &&
               Name == person.Name &&
               Age == person.Age;
    }

    public override int GetHashCode()
    {
        // GetHashCode() in .NET Core
        // https://bartwullems.blogspot.com/2024/01/gethashcode-in-net-core.html
        return HashCode.Combine(Name, Age);
    }

    /* 또는, https://montemagno.com/optimizing-c-struct-equality-with-iequatable/

    public bool Equals(Person other) => (Name, Age) == (other.Name, other.Age);

    public override int GetHashCode() => (Name, Age).GetHashCode();
    */
}

.NET Core 프로젝트부터 HashCode.Combine이 사용되며 .NET Framework 프로젝트에서는 다음과 같이 record에서와 유사한 코드가 생성됩니다.

// .NET Framework 프로젝트

public class Person
{
    public string Name;
    public int Age;

    public override bool Equals(object obj)
    {
        return obj is Person person &&
               Name == person.Name &&
               Age == person.Age;
    }

    public override int GetHashCode()
    {
        int hashCode = -1360180430;
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name);
        hashCode = hashCode * -1521134295 + Age.GetHashCode();
        return hashCode;
    }
}




다시 한번 말씀드리면, 어차피 4바이트 정숫값으로는 충돌을 피할 수 없으므로 GetHashCode에 많은 고민을 하실 필요는 없습니다. 단지, 충돌에 대비해 Equals만 제대로 정의하면 BCL 자료 구조 내에서의 동작에는 문제가 없습니다.

물론, 성능에 아주/엄청나게 민감한 응용 프로그램이라면 최대한 저 메서드를 능력껏 간소화시키시면 됩니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 2/2/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13835정성태12/2/202411152오류 유형: 934. Azure - rm: cannot remove '...': Directory not empty
13834정성태11/29/202411243Windows: 275. C# - CUI 애플리케이션과 Console 윈도우 (Windows 10 미만의 Classic Console 모드인 경우) [1]파일 다운로드1
13833정성태11/29/202410309개발 환경 구성: 737. Azure Web App에서 Scale-out으로 늘어난 리눅스 인스턴스에 SSH 접속하는 방법
13832정성태11/27/202410163Windows: 274. Windows 7부터 도입한 conhost.exe
13831정성태11/27/20248965Linux: 111. eBPF - BPF_MAP_TYPE_PERF_EVENT_ARRAY, BPF_MAP_TYPE_RINGBUF에 대한 다양한 용어들
13830정성태11/25/202411949개발 환경 구성: 736. 파이썬 웹 앱을 Azure App Service에 배포하기
13829정성태11/25/202411777스크립트: 67. 파이썬 - Windows 버전에서 함께 설치되는 py.exe
13828정성태11/25/20249709개발 환경 구성: 735. Azure - 압축 파일을 이용한 web app 배포 시 디렉터리 구분이 안 되는 문제파일 다운로드1
13827정성태11/25/202410968Windows: 273. Windows 환경의 파일 압축 방법 (tar, Compress-Archive)
13826정성태11/21/202410743닷넷: 2313. C# - (비밀번호 등의) Console로부터 입력받을 때 문자열 출력 숨기기(echo 끄기)파일 다운로드1
13825정성태11/21/202412107Linux: 110. eBPF (bpf2go) - BPF_RINGBUF_OUTPUT / BPF_MAP_TYPE_RINGBUF 사용법
13824정성태11/20/20249443Linux: 109. eBPF (bpf2go) - BPF_PERF_OUTPUT / BPF_MAP_TYPE_PERF_EVENT_ARRAY 사용법
13823정성태11/20/202411297개발 환경 구성: 734. Ubuntu에 docker, kubernetes (k3s) 설치
13822정성태11/20/202411080개발 환경 구성: 733. Windbg - VirtualBox VM의 커널 디버거 연결 시 COM 포트가 없는 경우
13821정성태11/18/202411126Linux: 108. Linux와 Windows의 프로세스/스레드 ID 관리 방식
13820정성태11/18/202411519VS.NET IDE: 195. Visual C++ - C# 프로젝트처럼 CopyToOutputDirectory 항목을 추가하는 방법
13819정성태11/15/20248399Linux: 107. eBPF - libbpf CO-RE의 CONFIG_DEBUG_INFO_BTF 빌드 여부에 대한 의존성
13818정성태11/15/202413686Windows: 272. Windows 11 24H2 - sudo 추가 [1]
13817정성태11/14/202410063Linux: 106. eBPF (bpf2go) - (BPF_MAP_TYPE_HASH) Map을 이용한 전역 변수 구현
13816정성태11/14/202411073닷넷: 2312. C#, C++ - Windows / Linux 환경의 Thread Name 설정파일 다운로드1
13815정성태11/13/20249234Linux: 105. eBPF - bpf2go에서 전역 변수 설정 방법
13814정성태11/13/202410457닷넷: 2311. C# - Windows / Linux 환경에서 Native Thread ID 가져오기파일 다운로드1
13813정성태11/12/202412278닷넷: 2310. .NET의 Rune 타입과 emoji 표현파일 다운로드1
13812정성태11/11/202415547오류 유형: 933. Active Directory - The forest functional level is not supported.
13811정성태11/11/202410124Linux: 104. Linux - COLUMNS 환경변수가 언제나 80으로 설정되는 환경
13810정성태11/10/202411524Linux: 103. eBPF (bpf2go) - Tracepoint를 이용한 트레이스 (BPF_PROG_TYPE_TRACEPOINT)
1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...