0Pricing
C# Academy · レッスン

等価性とハッシュ(値と参照)

参照の等価性と値の等価性を理解し、Equals/GetHashCodeを正しくオーバーライドし、IEquatable を実装し、集合や辞書でカスタム比較子を使います。

「等価性とハッシュ(値と参照)」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン3/3です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全3レッスンが含まれています。

等価性の基本

目標: 等価性が期待どおりに機能するようにします。

  • 参照等価性と値等価性
  • Equals と GetHashCode のコントラクト
  • 高速化のための IEquatable<T>
  • 型を変更できない場合のカスタム比較子

参照等価性の落とし穴

クラスはデフォルトでは参照等価性を使用します。値等価性を実装しない限り、同じデータを持つ別々のオブジェクトは等しいとはみなされません。

using System;
using System.Collections.Generic;

public sealed class Point // no overrides
{
  public int X;
  public int Y;
  public Point(int x, int y){ X = x; Y = y; }
}

public class Program
{
  public static void Main(string[] args)
  {
    Point a = new Point(1, 2);
    Point b = new Point(1, 2);

    Console.WriteLine("a == b ? " + (a == b)); // reference equality: False
    Console.WriteLine("a.Equals(b) ? " + a.Equals(b)); // False

    HashSet<Point> set = new HashSet<Point>();
    set.Add(a);
    Console.WriteLine("Contains b? " + set.Contains(b)); // False (unexpected)
  }
}

値等価性の実装

IEquatable<T> を実装し、Equals と GetHashCode をオーバーライドします。ハッシュ値は等価性と一致している必要があります。等しいオブジェクトは同じハッシュ値になります。

using System;
using System.Collections.Generic;

public sealed class ValuePoint : IEquatable<ValuePoint>
{
  public int X;
  public int Y;
  public ValuePoint(int x, int y){ X = x; Y = y; }

  public bool Equals(ValuePoint other)
  {
    if (ReferenceEquals(other, null)) return false;
    if (ReferenceEquals(this, other)) return true;
    return X == other.X && Y == other.Y;
  }

  public override bool Equals(object obj)
  {
    return Equals(obj as ValuePoint);
  }

  public override int GetHashCode()
  {
    // Simple, stable combination (avoid randomness)
    unchecked
    {
      int hash = 17;
      hash = hash * 31 + X.GetHashCode();
      hash = hash * 31 + Y.GetHashCode();
      return hash;
    }
  }
}

public class Program
{
  public static void Main(string[] args)
  {
    ValuePoint a = new ValuePoint(1, 2);
    ValuePoint b = new ValuePoint(1, 2);

    Console.WriteLine("a.Equals(b)? " + a.Equals(b)); // True

    HashSet<ValuePoint> set = new HashSet<ValuePoint>();
    set.Add(a);
    Console.WriteLine("Contains b? " + set.Contains(b)); // True (value semantics)
  }
}

カスタム比較子

型自体を変更できない場合は、カスタム IEqualityComparer<T> を HashSet/Dictionary に渡します。

using System;
using System.Collections.Generic;

public sealed class Person // imagine from a library; cannot edit
{
  public string Name;
  public int BirthYear;
  public Person(string name, int year){ Name = name; BirthYear = year; }
}

public sealed class PersonComparer : IEqualityComparer<Person>
{
  public bool Equals(Person a, Person b)
  {
    if (ReferenceEquals(a, b)) return true;
    if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) return false;
    return a.Name == b.Name && a.BirthYear == b.BirthYear;
  }

  public int GetHashCode(Person p)
  {
    if (ReferenceEquals(p, null)) return 0;
    unchecked
    {
      int h = 23;
      h = h * 31 + (p.Name == null ? 0 : p.Name.GetHashCode());
      h = h * 31 + p.BirthYear.GetHashCode();
      return h;
    }
  }
}

public class Program
{
  public static void Main(string[] args)
  {
    Person p1 = new Person("Ada", 1815);
    Person p2 = new Person("Ada", 1815);

    HashSet<Person> set = new HashSet<Person>(new PersonComparer());
    set.Add(p1);
    Console.WriteLine("Contains p2? " + set.Contains(p2)); // True via comparer
  }
}

コントラクトと落とし穴

ルール:

  • Equals(a,b) が true ならば ⇒ GetHashCode(a) == GetHashCode(b)
  • 等価性は反射的、対称的、推移的である必要があります
  • 可能な限り、ハッシュ計算には不変フィールドを使用します
  • GetHashCode でランダムな値を使用しないでください

構造体とクラスに関する注意

構造体はデフォルトではフィールドによって比較されます(値セマンティクス)。クラスは値等価性を実装しない限り、参照によって比較されます。

using System;

public struct PointS // struct: value type
{
  public int X;
  public int Y;
  public PointS(int x, int y){ X = x; Y = y; }
}

public class Program
{
  public static void Main(string[] args)
  {
    PointS a = new PointS(1, 2);
    PointS b = new PointS(1, 2);
    Console.WriteLine("Struct equality: " + a.Equals(b)); // True by default (field-wise)
  }
}

コレクションでの等価性コントラクト

確認問題: HashSet と Dictionary で機能するクラスの値等価性を実現するには、何を実装すべきでしょうか?

まとめ

まとめ: クラスで値セマンティクスを実現するには、Equals/GetHashCode(および多くの場合 IEquatable<T>)が必要です。型を編集できない場合はカスタム比較子を使用します。

よくある質問

「等価性とハッシュ(値と参照)」レッスンは無料ですか?

はい。「等価性とハッシュ(値と参照)」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全3レッスンが含まれています。

「等価性とハッシュ(値と参照)」で何を学びますか?

参照の等価性と値の等価性を理解し、Equals/GetHashCodeを正しくオーバーライドし、IEquatable を実装し、集合や辞書でカスタム比較子を使います。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C# Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/3です。

「等価性とハッシュ(値と参照)」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC# Academyレッスンでコードを書いて実行できますか?

はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. HashSet 、SortedSet 、Queue 、Stack
  2. ConcurrentDictionary とイミュータブルコレクション
  3. 等価性とハッシュ(値と参照)
← C# Academyに戻る