0Pricing
C# Academy · 강의

GetHashCode 재정의

같음 비교와 해시 계산의 일관성을 유지합니다.

GetHashCode 재정의은(는) CoddyKit의 무료 C# Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C# Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

GetHashCode가 중요한 이유

Dictionary와 HashSet 같은 해시 기반 컬렉션은 항목을 버킷에 배치할 때 GetHashCode를 사용합니다. Equals만 재정의하고 GetHashCode를 재정의하지 않으면 이러한 컬렉션이 같은 항목을 찾지 못할 수 있습니다.

황금률

규칙은 간단합니다. 두 객체가 같다면 반드시 같은 해시 코드를 반환해야 합니다. 그 역은 필수가 아닙니다. 서로 다른 객체가 같은 해시 코드를 공유할 수 있으며 이를 충돌이라고 합니다.

using System;

public struct Point
{
    public int X, Y;
    public Point(int x, int y) { X = x; Y = y; }
    public override bool Equals(object obj) => obj is Point p && p.X == X && p.Y == Y;
    public override int GetHashCode() => HashCode.Combine(X, Y);
}

public class Program
{
    public static void Main()
    {
        var a = new Point(1, 2);
        var b = new Point(1, 2);
        Console.WriteLine(a.Equals(b));
        Console.WriteLine(a.GetHashCode() == b.GetHashCode());
    }
}

HashCode.Combine 사용하기

HashCode.Combine 도우미는 여러 필드 값을 섞어 고르게 분포된 해시를 만듭니다. 이는 GetHashCode를 구현하는 데 권장되는 현대적인 방법입니다.

using System;

public struct Color
{
    public int R, G, B;
    public Color(int r, int g, int b) { R = r; G = g; B = b; }
    public override bool Equals(object obj) => obj is Color c && c.R == R && c.G == G && c.B == B;
    public override int GetHashCode() => HashCode.Combine(R, G, B);
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Color(255, 0, 0).GetHashCode() == new Color(255, 0, 0).GetHashCode());
    }
}

같음 판단과 해시를 함께 구현하기

항상 Equals와 GetHashCode를 한 쌍으로 재정의하고, 두 메서드에서 같은 필드를 사용하십시오. 서로 다른 필드를 사용하면 같은 객체가 서로 다른 해시를 생성하게 됩니다.

using System;

public class Book
{
    public string Title;
    public int Year;
    public Book(string title, int year) { Title = title; Year = year; }
    public override bool Equals(object obj)
        => obj is Book b && b.Title == Title && b.Year == Year;
    public override int GetHashCode() => HashCode.Combine(Title, Year);
}

public class Program
{
    public static void Main()
    {
        var a = new Book("C#", 2020);
        var b = new Book("C#", 2020);
        Console.WriteLine(a.GetHashCode() == b.GetHashCode());
    }
}

Dictionary에 필요한 이유

Dictionary는 먼저 키를 해시하여 버킷을 찾은 다음, 해당 버킷 안에서 Equals를 사용합니다. 잘못된 해시는 검색을 잘못된 버킷으로 보내므로 키를 절대 찾을 수 없습니다.

using System;
using System.Collections.Generic;

public struct Coord
{
    public int X, Y;
    public Coord(int x, int y) { X = x; Y = y; }
    public override bool Equals(object obj) => obj is Coord c && c.X == X && c.Y == Y;
    public override int GetHashCode() => HashCode.Combine(X, Y);
}

public class Program
{
    public static void Main()
    {
        var map = new Dictionary<Coord, string> { [new Coord(2, 3)] = "hit" };
        Console.WriteLine(map[new Coord(2, 3)]);
    }
}

변경 불가능한 필드 해싱하기

해시 코드 기반 컬렉션에 객체가 들어 있는 동안에는 해시 코드가 안정적이어야 합니다. 해시를 변경 불가능한 필드에 기반하여 계산하십시오. 삽입한 후 키가 변경되면 컬렉션이 해당 키를 추적하지 못할 수 있습니다.

using System;

public class Account
{
    public readonly int Id; // immutable, safe to hash
    public string Nickname; // mutable, do not hash
    public Account(int id, string nick) { Id = id; Nickname = nick; }
    public override bool Equals(object obj) => obj is Account a && a.Id == Id;
    public override int GetHashCode() => Id.GetHashCode();
}

public class Program
{
    public static void Main()
    {
        var acc = new Account(42, "old");
        int h1 = acc.GetHashCode();
        acc.Nickname = "new"; // does not change the hash
        Console.WriteLine(h1 == acc.GetHashCode());
    }
}

null 필드 처리하기

필드가 null일 수 있을 때 HashCode.Combine은 이를 안전하게 처리합니다. 직접 계산한다면 NullReferenceException을 피하도록 null을 확인하십시오.

using System;

public class Tag
{
    public string Name;
    public Tag(string name) { Name = name; }
    public override bool Equals(object obj) => obj is Tag t && t.Name == Name;
    public override int GetHashCode() => HashCode.Combine(Name); // null-safe
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(new Tag(null).GetHashCode() == new Tag(null).GetHashCode());
    }
}

충돌은 정상입니다

해시 코드는 32비트이므로 큰 데이터 집합에서는 충돌이 불가피합니다. 좋은 해시는 값을 고르게 분산하여 충돌을 드물게 만들고, 발생한 충돌은 Equals가 해결합니다.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        var set = new HashSet<int>();
        for (int i = 0; i < 5; i++) set.Add(i);
        // Equality still works perfectly even though hashing is imperfect in general
        Console.WriteLine(set.Contains(3));
        Console.WriteLine(set.Contains(99));
    }
}

레코드가 자동으로 생성합니다

record는 속성에서 올바른 GetHashCode를 자동으로 생성하며, 값 기반 Equals와 일치시킵니다. 해시를 올바르게 구현하는 가장 쉬운 방법입니다.

using System;
using System.Collections.Generic;

public record Coord(int X, int Y);

public class Program
{
    public static void Main()
    {
        var set = new HashSet<Coord> { new Coord(1, 1) };
        Console.WriteLine(set.Contains(new Coord(1, 1)));
    }
}

올바른 같음 가능 형식

모든 요소를 결합하면 다음과 같습니다. Equals와 GetHashCode에서 같은 필드를 사용하고, 변경 불가능한 키를 사용하며, 결합에는 HashCode.Combine을 사용합니다. 이렇게 만든 형식은 사전 키로 완벽하게 작동합니다.

using System;
using System.Collections.Generic;

public struct ProductKey
{
    public string Sku;
    public int Variant;
    public ProductKey(string sku, int variant) { Sku = sku; Variant = variant; }
    public override bool Equals(object obj)
        => obj is ProductKey k && k.Sku == Sku && k.Variant == Variant;
    public override int GetHashCode() => HashCode.Combine(Sku, Variant);
}

public class Program
{
    public static void Main()
    {
        var stock = new Dictionary<ProductKey, int>
        {
            [new ProductKey("ABC", 1)] = 10
        };
        Console.WriteLine(stock[new ProductKey("ABC", 1)]);
    }
}

직접 해 보기

규칙이 지켜지는지 확인해 보십시오. 같은 두 키를 만들고 같음 여부와 해시 코드가 모두 일치하는지 확인한 다음, 이를 set에서 사용하십시오.

using System;
using System.Collections.Generic;

public struct Name
{
    public string First, Last;
    public Name(string first, string last) { First = first; Last = last; }
    public override bool Equals(object obj) => obj is Name n && n.First == First && n.Last == Last;
    public override int GetHashCode() => HashCode.Combine(First, Last);
}

public class Program
{
    public static void Main()
    {
        var a = new Name("Ada", "Lovelace");
        var b = new Name("Ada", "Lovelace");
        Console.WriteLine(a.Equals(b) + " " + (a.GetHashCode() == b.GetHashCode()));
        var people = new HashSet<Name> { a };
        Console.WriteLine(people.Contains(b));
    }
}

빠른 확인

해싱 규칙을 떠올려 보십시오.

복습

올바른 해싱은 해시 기반 컬렉션이 제대로 작동하도록 합니다.

  • 같은 객체는 반드시 같은 해시 코드를 반환해야 합니다.
  • Equals에서 사용하는 것과 같은 필드에 HashCode.Combine을 사용하십시오.
  • 키를 계속 찾을 수 있도록 변경 불가능한 필드를 해싱하십시오.
  • 충돌은 정상이며 Equals가 이를 해결합니다.
  • 레코드는 올바른 구현을 자동으로 생성합니다.

자주 묻는 질문

“GetHashCode 재정의” 강의는 무료인가요?

네 — “GetHashCode 재정의” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C# Academy 강의 전체를 잠금 해제할 수 있습니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“GetHashCode 재정의”에서 뭘 배우나요?

같음 비교와 해시 계산의 일관성을 유지합니다. 브라우저에서 직접 실행하는 실습 코드로 C# Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

C# Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 C# Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“GetHashCode 재정의” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 C# Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 C# Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. IEquatable 구현
  2. GetHashCode 재정의
  3. IComparable 구현
  4. 사용자 지정 정렬을 위한 IComparer
← C# Academy(으)로 돌아가기