bucket-sort logo bucket-sort

プログラミングとインフラエンジニアリングの覚え書き

  • Posts
  • About
  • Contact
  1. Home
  2. All Posts
  3. [C#] 演算子オーバーロード(Operator Overloading)の使い方 — 二項演算子・単項演算子・等価演算子・比較演算子

[C#] 演算子オーバーロード(Operator Overloading)の使い方 — 二項演算子・単項演算子・等価演算子・比較演算子

Jun 5, 2026 C# , .NET bucket-sort

C# では、+ や - などの演算子を自作型に対して定義できます。これを operator overloading と呼びます。

たとえばベクトル、金額、日付、版番号など、演算の意味が自然に決まる型では、Add(a, b) のようなメソッドよりも a + b のほうが読みやすくなります。

この記事では次の 5 つを順に説明します。

  1. 二項演算子のオーバーロード
  2. += と -= 演算子はどうなる?
  3. 単項演算子のオーバーロード
  4. 等価演算子のオーバーロード
  5. 比較演算子のオーバーロード

1. 二項演算子のオーバーロード

二項演算子は、左辺と右辺の 2 つの値を受け取る演算子です。C# では +、-、*、/、%、&、|、^ などをオーバーロードできます。

ここでは 2 次元ベクトル Vector2 を例に、+ と - を定義します。

public readonly struct Vector2
{
    public double X { get; }
    public double Y { get; }

    public Vector2(double x, double y)
    {
        X = x;
        Y = y;
    }

    public static Vector2 operator +(Vector2 left, Vector2 right)
        => new(left.X + right.X, left.Y + right.Y);

    public static Vector2 operator -(Vector2 left, Vector2 right)
        => new(left.X - right.X, left.Y - right.Y);

    public override string ToString() => $"({X}, {Y})";
}
var a = new Vector2(3, 4);
var b = new Vector2(10, 20);

Console.WriteLine(a + b); // (13, 24)
Console.WriteLine(b - a); // (7, 16)

ポイントは、演算子の実装は static メソッドとして書く ことです。operator キーワードの後に演算子記号を置き、戻り値と引数の型を定義します。

2. += と -= 演算子はどうなる?

+= や -= は見た目としては演算子ですが、C# では直接オーバーロードできません。代わりに、対応する二項演算子 + や - を定義しておけば、+= や -= はそれを使って処理されます。

つまり、次のようなコードは:

var value = new Vector2(1, 2);
value += new Vector2(3, 4);

内部的にはおおむね次のように扱われます。

value = value + new Vector2(3, 4);

さきほどの Vector2 に + が定義されていれば、+= も自然に使えます。

var value = new Vector2(1, 2);
value += new Vector2(3, 4);

Console.WriteLine(value); // (4, 6)

+= や -= を個別に実装する必要はありません。まず + と - を正しく設計する のが重要です。

3. 単項演算子のオーバーロード

単項演算子は、1 つの値に対して働く演算子です。C# では +、-、!、~、++、-- などをオーバーロードできます。

ここでは Vector2 に対して単項 + と単項 - を追加します。単項 - は向きを反転するのに自然です。

public readonly struct Vector2
{
    public double X { get; }
    public double Y { get; }

    public Vector2(double x, double y)
    {
        X = x;
        Y = y;
    }

    public static Vector2 operator +(Vector2 value)
        => value;

    public static Vector2 operator -(Vector2 value)
        => new(-value.X, -value.Y);
}
var v = new Vector2(3, -5);

Console.WriteLine(+v); // (3, -5)
Console.WriteLine(-v); // (-3, 5)

++ と -- は、値を 1 つ増減する型で使うことがあります。たとえばカウンター型なら次のように書けます。

public struct Counter
{
    public int Value { get; private set; }

    public Counter(int value) => Value = value;

    public static Counter operator ++(Counter value)
        => new(value.Value + 1);

    public static Counter operator --(Counter value)
        => new(value.Value - 1);

    public override string ToString() => Value.ToString();
}
var counter = new Counter(10);
counter++;
counter--;

Console.WriteLine(counter); // 10

単項演算子は便利ですが、意味が直感的な型に限定して使う のが安全です。無理に定義すると、コードの意図が読みづらくなります。

4. 等価演算子のオーバーロード

等価演算子 == と != は、オブジェクト同士が同じかどうかを比較します。C# ではこの 2 つをペアで定義するのが基本です。

ここでは金額を表す Money を例にします。通貨と金額が一致するときに等しいとみなします。

using System;

public readonly struct Money : IEquatable<Money>
{
    public decimal Amount { get; }
    public string Currency { get; }

    public Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency ?? throw new ArgumentNullException(nameof(currency));
    }

    public bool Equals(Money other)
        => Amount == other.Amount && Currency == other.Currency;

    public override bool Equals(object? obj)
        => obj is Money other && Equals(other);

    public override int GetHashCode()
        => HashCode.Combine(Amount, Currency);

    public static bool operator ==(Money left, Money right)
        => left.Equals(right);

    public static bool operator !=(Money left, Money right)
        => !left.Equals(right);

    public override string ToString() => $"{Amount} {Currency}";
}
var a = new Money(100m, "JPY");
var b = new Money(100m, "JPY");
var c = new Money(100m, "USD");

Console.WriteLine(a == b); // True
Console.WriteLine(a != c); // True

等価演算子を定義する場合は、Equals と GetHashCode も必ず整合させる 必要があります。== が true なら Equals も true になるように揃えてください。

5. 比較演算子のオーバーロード

比較演算子 >、<、>=、<= は、大小関係を表します。これらもオーバーロードできますが、4 つをセットで定義する のが基本です。

ここでは VersionNumber を例に、メジャー、マイナー、パッチの順で比較できるようにします。

using System;

public readonly struct VersionNumber : IComparable<VersionNumber>, IEquatable<VersionNumber>
{
    public int Major { get; }
    public int Minor { get; }
    public int Patch { get; }

    public VersionNumber(int major, int minor, int patch)
    {
        Major = major;
        Minor = minor;
        Patch = patch;
    }

    public int CompareTo(VersionNumber other)
    {
        var majorCompare = Major.CompareTo(other.Major);
        if (majorCompare != 0) return majorCompare;

        var minorCompare = Minor.CompareTo(other.Minor);
        if (minorCompare != 0) return minorCompare;

        return Patch.CompareTo(other.Patch);
    }

    public bool Equals(VersionNumber other)
        => Major == other.Major && Minor == other.Minor && Patch == other.Patch;

    public override bool Equals(object? obj)
        => obj is VersionNumber other && Equals(other);

    public override int GetHashCode()
        => HashCode.Combine(Major, Minor, Patch);

    public static bool operator ==(VersionNumber left, VersionNumber right)
        => left.Equals(right);

    public static bool operator !=(VersionNumber left, VersionNumber right)
        => !left.Equals(right);

    public static bool operator <(VersionNumber left, VersionNumber right)
        => left.CompareTo(right) < 0;

    public static bool operator >(VersionNumber left, VersionNumber right)
        => left.CompareTo(right) > 0;

    public static bool operator <=(VersionNumber left, VersionNumber right)
        => left.CompareTo(right) <= 0;

    public static bool operator >=(VersionNumber left, VersionNumber right)
        => left.CompareTo(right) >= 0;

    public override string ToString() => $"{Major}.{Minor}.{Patch}";
}
var v1 = new VersionNumber(1, 2, 0);
var v2 = new VersionNumber(1, 3, 0);
var v3 = new VersionNumber(1, 3, 0);

Console.WriteLine(v1 < v2);  // True
Console.WriteLine(v2 > v1);  // True
Console.WriteLine(v2 == v3); // True
Console.WriteLine(v1 <= v3); // True

比較演算子を定義する型では、IComparable<T> を実装して CompareTo を用意しておくと、List<T>.Sort() などの標準機能とも相性が良くなります。

実装時の注意点

演算子オーバーロードは強力ですが、乱用すると読みやすさを損ないます。特に次の点は意識しておくと安全です。

  1. 意味が自然な型にだけ使う
  2. == と !=、< と >、<= と >= は整合させる
  3. 等価演算子は Equals と GetHashCode も揃える
  4. += や -= は個別にオーバーロードできないので、対応する二項演算子を実装する

まとめ

C# の operator overloading を使うと、自作型を配列や数値のように自然に扱えます。

トピック ポイント
二項演算子のオーバーロード + や - を static operator で定義する
+= / -= 直接は定義せず、+ / - を用意する
単項演算子のオーバーロード +、-、++、-- などを 1 引数で定義する
等価演算子のオーバーロード == と != をペアで定義し、Equals も整える
比較演算子のオーバーロード >, <, >=, <= をセットで定義し、IComparable<T> も実装する

演算子を使うとコードは短くなりますが、その短さが読みやすさにつながる場合にだけ採用する のがコツです。意味が明快なドメインモデルに絞って使うと、C# の表現力をうまく引き出せます。

C# .NET Operator Overloading 演算子オーバーロード Operators
← [C#] インデクサー [] (Indexer Methods)の使い方 — 文字列キー・オーバーロード・多次元・インターフェース定義まで [C#] カスタム型変換(Custom Type Conversions)の使い方 — explicit と implicit を使いこなす →

Related Posts

  • [C#] プラグインで拡張できるアプリケーションを設計する Jul 9, 2026
  • [C#] 属性でコードに意味を与える Jul 8, 2026
  • [C#] 外部アセンブリを読み込み実行時に型を呼び出す Jul 7, 2026
  • [C#] リフレクションで型の中身を調べる Jul 6, 2026

Table of Contents

  • 1. 二項演算子のオーバーロード
  • 2. += と -= 演算子はどうなる?
  • 3. 単項演算子のオーバーロード
  • 4. 等価演算子のオーバーロード
  • 5. 比較演算子のオーバーロード
  • 実装時の注意点
  • まとめ

Recent Posts

  • [C#] プラグインで拡張できるアプリケーションを設計する Jul 9, 2026
  • [C#] 属性でコードに意味を与える Jul 8, 2026
  • [C#] 外部アセンブリを読み込み実行時に型を呼び出す Jul 7, 2026
  • [C#] リフレクションで型の中身を調べる Jul 6, 2026
  • [C#] 型メタデータとリフレクションの基礎を理解する Jul 5, 2026

Categories

  • C#120
  • .NET119
  • AWS27
  • Laravel16
  • Linux15
  • MySQL9
  • Apache8
  • PHP8
  • DynamoDB6
  • セキュリティ6
  • Nginx5
  • WordPress4
  • インフラ4
  • Hugo3
  • .NET Framework1
  • Aurora1
  • Diagnostics1
  • Filament1
  • Git1
  • SQS1

Tags

  • C#
  • .NET
  • AWS
  • Laravel
  • コレクション
  • PHP
  • セキュリティ
  • MySQL
  • Linux
  • パフォーマンス
  • Apache
  • System.Collections.Generic
  • デリゲート
  • Code Snippet
  • DynamoDB
  • LINQ
  • NoSQL
  • PHP-FPM
  • RDS
  • System.Collections
  • Windows
  • メモリ管理
  • リフレクション
  • DoS
  • Nginx
  • WordPress
  • メタデータ
  • ラムダ式
  • 監視
  • 設計
  • Amazon Linux 2023
  • Delegate
  • Docker
  • IDisposable
  • Ipset
  • Iptables
  • LINQ to Objects
  • OPCache
  • Pointer
  • Reflection
  • System.Collections.Specialized
  • Unsafe
  • Webサーバー
  • インターフェース
  • オブジェクト指向
  • クラス設計
  • デザインパターン
  • パターンマッチング
  • ポインター
  • 継承
Powered by Hugo & Explore Theme.