オーバーロード(overload)を理解しよう!

[C#] オーバーロード(overload)を理解しよう!

※ 当サイトは広告を含みます。

ここではC#のオーバーロードについて学びます。
なお、似た名前にオーバーライドって機能がありましたが、両者は全く関係がありません。

管理人

もちろん骸骨の異世界系アニメでもありません。

りさ

分かるわ。

オーバーロード(overload)

オーバーロードとは、引数が異なるメソッドを同名で定義できる機能です。
これは例を見たほうが早いので、次のサンプルコードを実行してください。


namespace Sample
{
  public class Calculation
  {
    // 引数の異なる同名のメソッドを定義
    public int Sum(int x, int y)
    {
      return x + y;
    }

    // 引数の異なる同名のメソッドを定義
    public double Sum(double x, double y)
    {
      return x + y;
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      var c = new Calculation();

      System.Console.WriteLine(c.Sum(10, 20));
      System.Console.WriteLine(c.Sum(3.14, 2.0));
    }
  }
}

このように引数が異なれば同名のメソッドを定義しても許される仕組みオーバーロードです。
異なるの定義は引数の型や数です。引数で利用してる変数の名前(引数名)のみが異なる場合は駄目です。

結論として、各メソッドをコンパイラ(C#の言語解析)が別のメソッドとして認識できることが条件です。
ちなみに引数が同じで、戻り値のみ異なるメソッドもオーバーロードは不可能です。

つまり、こんな感じのコードはエラーでビルドできません。


namespace Sample
{
  public class Calculation
  {
    public int Sum(int x, int y)
    {
      return x + y;
    }

    // ビルドエラー
    public double Sum(int x, int y)
    {
      return x + y;
    }
  }
}

利用時の注意

同名メソッドの定義に機能的な縛りはありません。つまり、全く関係のない機能を同名メソッドで定義できます。
しかし、当然そんなことをすればメソッドの意味が分かりづらくなるのみで、何のメリットもありません。

管理人

メソッドの名前は非常に大切です。例え命名に時間が掛かっても正しい名前を付けましょう。

よくある利用例

オーバーロードの仕組みで1番便利なのがコードの共通化です。
よくある例のサンプルコードを用意したので、実行してみましょう。


namespace Sample
{
  internal class Number
  {
    private int x;
    private int y;
    private int z;

    public Number()
    {
    }

    // オーバーロード
    public void Initialization(int x)
    {
      // 自分自身の同名メソッドを呼び出す
      this.Initialization(x, 0);
    }

    // オーバーロード
    public void Initialization(int x, int y)
    {
      // 自分自身の同名メソッドを呼び出す
      this.Initialization(x, y, 0);
    }

    // オーバーロード
    public void Initialization(int x, int y, int z)
    {
      // 初期化のコードを共通化
      this.x = x;
      this.y = y;
      this.z = z;
    }

    public void Display()
    {
      System.Console.WriteLine($"x:{this.x}, y:{this.y}, z:{this.z}");
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Number n1 = new Number();
      Number n2 = new Number();
      Number n3 = new Number();

      n1.Initialization(5);
      n2.Initialization(10, 20);
      n3.Initialization(100, 150, 200);

      n1.Display();
      n2.Display();
      n3.Display();
    }
  }
}

このようにオーバーロードした自分自身のメソッドを順番に呼び出し、引数が1番多いメソッドに処理を統一することができます。
人によってはprivateな初期化メソッドを用意して、オーバーロードしたメソッドから、そのメソッドを呼び出す人もいます。


private void _Initialization(int x, int y, int z)
{
  this.x = x;
  this.y = y;
  this.z = z;
}
管理人

引数が1つ多いメソッドを順番に呼ぶか、1番引数の多いメソッドを一気に呼ぶかは趣味です。

コンストラクタのオーバーロード

コンストラクタもメソッドの一種ですが、任意の名前を付けることはできません。
つまり、引数なしコンストラクタや引数ありコンストラクタもオーバーロードの仕組みで動きます。


namespace Sample
{
  internal class Person
  {
    private string name;
    private int age;

    public Person()
    {
      this.name = "赤ちゃん";
      this.age = 0;
    }

    public Person(string name)
    {
      this.name = name;
      this.age = 20;
    }

    public Person(string name, int age)
    {
      this.name = name;
      this.age = age;
    }

    public void Display()
    {
      System.Console.WriteLine($"名前:{this.name}, 年齢:{this.age}");
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Person p1 = new Person();
      Person p2 = new Person("名無し");
      Person p3 = new Person("りさ", 15);

      p1.Display();
      p2.Display();
      p3.Display();
    }
  }
}

コンストラクタからコンストラクタを呼ぶ方法

コンストラクタから別のコンストラクタを呼ぶ場合は少し記述が特殊です。


namespace Sample
{
  internal class Number
  {
    private int x;
    private int y;
    private int z;

    // オーバーロード (別のコンストラクタを呼び出す)
    public Number()
      : this(0)
    {
    }

    // オーバーロード (別のコンストラクタを呼び出す)
    public Number(int x)
      : this(x, 0)
    {
    }

    // オーバーロード (別のコンストラクタを呼び出す)
    public Number(int x, int y)
      : this(x, y, 0)
    {
    }

    // オーバーロード
    public Number(int x, int y, int z)
    {
      // 初期化のコードを共通化
      this.x = x;
      this.y = y;
      this.z = z;
    }

    public void Display()
    {
      System.Console.WriteLine($"x:{this.x}, y:{this.y}, z:{this.z}");
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Number n1 = new Number(5);
      Number n2 = new Number(10, 20);
      Number n3 = new Number(100, 150, 200);

      n1.Display();
      n2.Display();
      n3.Display();
    }
  }
}

該当する部分は以下です。このように:(コロン)this(引数)を利用して呼び出します。


public Number(int x, int y)
  : this(x, y, 0)
{
}

ただし、値を渡すのみで処理は書けません。値の加工が必要な場合は素直にprivateな初期化メソッドを作りましょう。
なお、別のコンストラクタを呼び出す場合はthis()で指定したコンストラクタから順番に処理されます。


namespace Sample
{
  internal class Number
  {
    public Number()
      : this(0)
    {
      System.Console.WriteLine($"引数0");
    }

    public Number(int x)
      : this(x, 0)
    {
      System.Console.WriteLine($"引数1");
    }

    public Number(int x, int y)
      : this(x, y, 0)
    {
      System.Console.WriteLine($"引数2");
    }

    public Number(int x, int y, int z)
    {
      System.Console.WriteLine($"引数3");
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Number n3 = new Number();
    }
  }
}
管理人

こんな感じに全部を呼び出すと引数が多い順に実行されます。メソッドからメソッドを呼ぶのと同じ仕組みです。

演算子のオーバーロード

C#は演算子もオーバーロードすることが可能です。

オーバーロードの可否は演算子によって異なりますが、+-みたいな簡単な演算子はオーバーロードが可能です。
もし可能と不可能の演算子に興味がある人は、以下のリンク先で確認できるので参考にしてください。
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/operator-overloading#overloadable-operators

以下は+演算子をオーバーロードしたサンプルコードです。


namespace Sample
{
  internal class Number
  {
    private int x;

    // +演算子をオーバーロード
    public static Number operator +(Number n1, Number n2)
    {
      return new Number(n1.x + n2.x);
    }

    public Number(int x)
    {
      this.x = x;
    }

    public void Display()
    {
      System.Console.WriteLine($"数値:{this.x}");
    }
  }

  internal class Program
  {
    static void Main(string[] args)
    {
      Number n1 = new Number(7);
      Number n2 = new Number(5);

      // 自作のクラス同士で+演算子が使える
      Number n3 = n1 + n2;

      n1.Display();
      n2.Display();
      n3.Display();
    }
  }
}

正直あんまり使いません。たぶん、自分でライブラリを開発するようになると使うかもしれません。
とりあえず、構文の確認で該当する部分だけ抜き出すと以下です。


public static Number operator +(Number n1, Number n2)
{
  return new Number(n1.x + n2.x);
}

そして構文は以下です。


public static 戻り値の型 operator 演算子(引数)
{
  // 処理
}

普通のメソッドをオーバーロードするのとは異なり、いくつか記述に制約があります。

  1. 必ずpublic staticで記述する。
  2. 単項演算子(引数1個)や2項演算子(引数2個)の仕様を破壊する記述は不可能。
  3. 引数の1つは必ず自分自身の型(クラス名)を使う。

となります。1つ目はそういうもの、3つ目もそうでないと呼び出しが不可能だから。
2つ目は少し難しく書いてますが、単項演算子は単項演算子として、2項演算子は2項演算子として記述する必要があります。
要は単項演算子を引数が2つの2項演算子に改造するような記述は不可能です。

管理人

滅多に使わないから構文を覚えられない。

りさ

最後に使ったのいつ?

管理人

いつだろうね。正直、使いたい時に調べればいいと思うよ。

あとがき

メソッドも演算子のオーバーロードも中身の実装に制限はありません。例えば+演算子を使って-の演算をする処理を書くことができます。
まぁ、そんなことをしたら殴られますが、可能か不可能で言えば可能です。最低でも意味が理解できるオーバーロードを心掛けましょう。

◆ C#に関する学習コンテンツ

この記事は参考になりましたか?

関連記事

コメント

この記事へのコメントはありません。