導入
C#のすべての型は、共通の親objectを継承しています。だからToString()・Equals()・GetHashCode()はどんなオブジェクトにもあります。これらを正しくオーバーライドすると、「意味的に等しいか」を自分で定義でき、DictionaryやHashSetのキーとしても正しく振る舞います。あわせて継承を禁じるsealed、定義を分割するpartialも学びます。
図解
flowchart TB
O["object(すべての型の親)"] --> M["ToString() / Equals() / GetHashCode()"]
M --> D["既定は参照(同じインスタンスか)で比較"]
D --> OV["override すると<br/>「中身が同じなら等しい」に変えられる"]
style OV fill:#e8f5e9
サンプル
class Money
{
public int Amount { get; }
public Money(int amount) => Amount = amount;
// 「金額が同じなら等しい」と定義する
public override bool Equals(object? obj) => obj is Money m && m.Amount == Amount;
// Equals を変えたら GetHashCode も揃える(HashSet/Dictionary のため)
public override int GetHashCode() => Amount.GetHashCode();
// 表示も分かりやすく
public override string ToString() => $"{Amount}円";
}
var a = new Money(100);
var b = new Money(100);
Console.WriteLine(a.Equals(b)); // True(中身が同じ)
Console.WriteLine(a); // 100円(ToStringが効く)
// GetHashCode を揃えたので、集合でも「同じ100円」として扱える
var set = new HashSet<Money> { a, b };
Console.WriteLine(set.Count); // 1
- すべての型は
objectを継承し、ToString/Equals/GetHashCodeを持つ - 既定は参照の比較(同じインスタンスか)。
overrideで「中身が同じなら等しい」に変えられる Equalsを変えたら必ずGetHashCodeも揃える(HashSet/Dictionaryが正しく動くため)sealed classは継承を禁止、partial classはクラス定義を複数ファイルに分割できる(自動生成コードとの併用で頻出)
演習
var p1 = new Point(1, 2);
var p2 = new Point(1, 2);
// TODO: Point の Equals をオーバーライドし、X と Y が同じなら等しいと判定させてください
// (p1.Equals(p2) が True になる)
Console.WriteLine(p1.Equals(p2));
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) { X = x; Y = y; }
___
}
- 期待される出力:
True
ヒント1を見る
public override bool Equals(object? obj) => obj is Point p && p.X == X && p.Y == Y;
ヒント2を見る
上の1行をPointクラスの中に加えます
まとめ
- すべての型は
objectを継承し、ToString/Equals/GetHashCodeを持つ - 「中身が同じ=等しい」にしたいなら
EqualsとGetHashCodeをセットでオーバーライド sealedは継承禁止、partialは定義の分割
次回: 添字アクセスや+を自作型に持たせる「インデクサーと演算子オーバーロード」です。