CodeGym /コース /C# SELF /メソッドのオーバーロード (Method Overloading)

メソッドのオーバーロード (Method Overloading)

C# SELF
レベル 21, レッスン 1
使用可能

1. はじめに

リアルな生活でも、いろんな動作ってまるで多機能なスイスアーミーナイフみたいなもんだよね:同じコマンドでも使うツールが違うだけ。例えばATMを想像してみて:カードを入れたらPINコードを聞かれるし、電話番号を入力したらSMSの認証コードを待つ。やることは「ユーザーを確認する」だけど、やり方が違うんだ。

プログラミングでも似たような状況がよくあるよ:一つの操作をしたいけど、データの型やパラメータの数が違う場合がある。例えば、メソッドで人にも動物にも挨拶したいとか、2つ・3つ・10個の整数を合計したいとかね。

もちろん、メソッド名を全部変えてもいいけど:SumTwo、SumThree、SumArrayみたいに。でもプログラマーは基本めんどくさがり(怠け者は進歩の原動力って言うしね)。しかも、そうするとコードが読みにくくなる。

メソッドのオーバーロード

メソッドのオーバーロードは、同じメソッド名でパラメータの組み合わせが違うメソッドを「使い分ける」やり方だよ。これはポリモーフィズムの一種だけど、継承とは関係ないんだ。

オーバーロードは、1つのクラス(またはstruct)で同じ名前のメソッドを複数作れるってこと。ただし、パラメータの型・数・順番が違う必要があるよ。

メソッドのシグネチャ

C#でのシグネチャは、メソッド名+パラメータの型と順番のこと。戻り値の型はシグネチャに含まれない! これが原因で意外なエラーが出ることもある(後で説明するね)。

2. オーバーロードの実例:シンプルなサンプル

じゃあ、Greeterクラスを作ってみよう。これはユーザーにいろんな挨拶をする:名前だけ、名前と年齢、またはパラメータなし。


public class Greeter
{
    // パラメータなしの挨拶
    public void Greet()
    {
        Console.WriteLine("こんにちは、世界!");
    }

    // 名前付きの挨拶
    public void Greet(string name)
    {
        Console.WriteLine($"こんにちは、{name}!");
    }

    // 名前と年齢付きの挨拶
    public void Greet(string name, int age)
    {
        Console.WriteLine($"こんにちは、{name}!もう{age}歳?すごいね!");
    }
}

これでどのメソッドも呼び出せるし、C#のコンパイラがパラメータの数や型を見て自動で正しいバージョンを選んでくれるよ。

var greeter = new Greeter();
greeter.Greet();                // こんにちは、世界!
greeter.Greet("アーニャ");      // こんにちは、アーニャ!
greeter.Greet("ピョートル", 23); // こんにちは、ピョートル!もう23歳?すごいね!

3. パラメータの型と数の違い

オーバーロードは、メソッドが次の点で違えばOK:

  • パラメータの数
  • 少なくとも1つのパラメータの型
  • パラメータ型の順番(ただし注意が必要)

年齢だけを受け取るオーバーロードも追加してみよう:


public void Greet(int age)
{
    Console.WriteLine($"その年齢、カッコいいね!({age}歳)");
}

呼び出し例:

greeter.Greet(10);          // その年齢、カッコいいね!(10歳)

重要:戻り値の型だけが違う場合はオーバーロードできない。例えば、こんなコードはエラーになる:


// コンパイルエラー!
public int Foo(string s) { ... }
public double Foo(string s) { ... }
戻り値の型だけでのオーバーロードはできない!

コンパイラは「もうFoo(string)ってメソッドあるよ、もっと工夫して!」って怒るよ。

4. C#標準ライブラリとオーバーロード

オーバーロードは自作のGreetだけじゃない。.NETのConsole.WriteLineのドキュメントを見てみよう:

シグネチャ 用途
WriteLine()
空行を出力
WriteLine(string)
文字列を出力
WriteLine(int)
整数を出力
WriteLine(double)
小数を出力
WriteLine(string, object)
1つの引数で文字列をフォーマット
WriteLine(string, params object[])
複数引数でフォーマット

これ全部同じWriteLineメソッドのオーバーロードだよ。だからいつでもこう書けるんだ:

Console.WriteLine("ただの文字列");
Console.WriteLine(123);
Console.WriteLine(2.5);
Console.WriteLine("合計: {0}", 42);

そしてコンパイラはいつも正しく解釈してくれる!

5. コンパイラはどのオーバーロードを選ぶの?

ここは厳密だよ:実際に渡された引数の型と数を見て決める。分かりやすく表にしてみた:

呼び出し どのバージョン?
greeter.Greet()
void Greet()
greeter.Greet(75)
void Greet(int)

曖昧な場合はどうなる?

たまに状況がカオスになる。曖昧なオーバーロードの例:コンパイラがどれを選ぶか決められない


public void Print(int a, double b) { ... }
public void Print(double a, int b) { ... }

printer.Print(5, 10); 
// エラー:曖昧すぎてどのPrintを呼ぶか分からない(どっちも合いそう)

コンパイラは曖昧エラーを出す。こういう場合は、パラメータ数が同じで型が近いオーバーロードは避けた方がいいよ。

6. params — 可変長パラメータ

例えば、いくつでも数値を受け取るメソッドを作りたいとき。そんな時はparamsキーワードが便利。


public void SumAll(params int[] numbers)
{
    int sum = 0;
    foreach (int n in numbers)
        sum += n;
    Console.WriteLine($"合計: {sum}");
}
可変長パラメータのメソッド ( params)

これでこう呼べる:

SumAll(1, 2, 3);           // 合計: 6
SumAll(10, 20);            // 合計: 30
SumAll();                  // 合計: 0

params付きメソッドはオーバーロードと組み合わせてもOKだけど、どのバージョンを呼ぶかコンパイラが迷わないように注意してね。

7. オーバーロードとパラメータ修飾子 (ref, out, in)

C#ではパラメータ修飾子もシグネチャに含まれる(つまり、void Foo(int a)とvoid Foo(ref int a)は別物として共存できる):

public void SetValue(int a)
{
    a = 42;
}

public void SetValue(ref int a)
{
    a = 100;
}
refによるオーバーロード

refなしは最初のバージョン、refありは2つ目に行くよ:

int n = 5;
SetValue(n);     // nは5のまま(値がコピーされる)
SetValue(ref n); // nは100になる

8. 図解:オーバーロードって何?


      +----------+
      |  MyClass |
      +----------+
           |
           |            (メソッドの一部)
    +-----------------------+
    |   void Foo()          |
    |   void Foo(int a)     |
    |   void Foo(string s)  |
    |   void Foo(int a, int b) |
    +-----------------------+
クラス内のメソッドオーバーロードの図

コードで表すと:

// オーバーロードされたFoo()を呼ぶ:
var mc = new MyClass();
mc.Foo();              // void Foo()
mc.Foo(5);             // void Foo(int)
mc.Foo("Hello");       // void Foo(string)
mc.Foo(2, 3);          // void Foo(int, int)

9. サンプル:自作アプリでメソッドをオーバーロードしよう

じゃあ、学習用アプリを進化させて、動物クラスの階層にメソッドのオーバーロードを追加しよう。


public class Animal
{
    public string Name { get; set; }

    // サウンドを出すメソッド
    public virtual void MakeSound()
    {
        Console.WriteLine("なんかよく分からない音…");
    }

    // オーバーロード:音量指定
    public void MakeSound(int volume)
    {
        Console.WriteLine($"動物が{volume}dBの音を出す。");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("ワン!");
    }

    // オーバーロード:音量付きのワン!
    public void MakeSound(int volume)
    {
        Console.WriteLine($"ワン!(音量: {volume}dB)");
    }
}

こんな呼び方を試してみて:

Dog rex = new Dog();
rex.MakeSound();           // ワン!
rex.MakeSound(75);         // ワン!(音量: 75dB)

注目ポイント:サブクラス(Dog)でMakeSound(int volume)をオーバーロードしたから、パラメータあり・なし両方のバージョンが使えるよ。

10. メソッドオーバーロードでよくあるミス

ミス1:戻り値の型だけでオーバーロードしようとする。
これは無理!戻り値の型はシグネチャに含まれない。オーバーロードは入力パラメータの数や型で区別しよう。voidやintだけじゃダメ。

ミス2:曖昧なオーバーロードでコンパイラを混乱させる。
パラメータ数が同じで型が近い(例:intとdouble)と、コンパイラが混乱する。例:Print(int a, double b)とPrint(double a, int b)、Print(1, 1)を呼ぶと曖昧エラー。

ミス3:paramsと他のオーバーロードがバッティング。
params付きメソッドが他のオーバーロードを「横取り」することがある。型が同じだと、コンパイラが意図しない方を選ぶことも。

ミス4:refやoutもシグネチャに含まれるのを忘れる。
Do(ref int x)とDo(out int x)は別のオーバーロード。これを忘れると、間違ったバージョンを呼びやすいよ。

2
タスク
C# SELF, レベル 21, レッスン 1
ロック未解除
図形の面積を計算するためのオーバーロードされたメソッドの作成
図形の面積を計算するためのオーバーロードされたメソッドの作成
2
タスク
C# SELF, レベル 21, レッスン 1
ロック未解除
フォーマットされたデータ出力用のメソッド
フォーマットされたデータ出力用のメソッド
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION