本文へスキップ
ウェブエンジニア問題集
第5章

C#のメソッド — 引数・戻り値・ref/outとオーバーロード

約6分
この章の目次開く

メソッドは処理をまとめる単位です。基本の書き方はJavaScriptの関数と大差ありませんが、C#には引数の渡し方が複数あるという特徴があります。ここを理解しておくと、標準ライブラリのシグネチャが読めるようになります。

学習者学習者

int.TryParse(input, out var n) の out は何だったんでしょうか。ずっと気になっていました。

この章でその正体を扱います。

メソッドの基本形

public int Add(int a, int b)
{
    return a + b;
}
csharp
位置要素意味
publicアクセス修飾子どこから呼べるか
int戻り値の型返す値の型。返さないなら void
Addメソッド名慣習としてパスカルケース(先頭大文字)
(int a, int b)引数リスト受け取る値と型

値を返さない場合は void を指定し、return; で途中終了できます。

public void PrintName(string name)
{
    if (string.IsNullOrEmpty(name)) return;
    Console.WriteLine(name);
}
csharp

引数の渡し方

C#では、引数の前に付けるキーワードで渡し方を変えられます。

書き方渡るもの呼び出し側での初期化主な用途
(なし)値のコピー必要通常の引数
ref変数そのものへの参照必要呼び出し元の変数を書き換える
out変数そのものへの参照不要結果を追加で返す
in読み取り専用の参照必要大きな値型をコピーせず渡す
public void Increment(ref int value)
{
    value++;
}
 
int count = 5;
Increment(ref count);
Console.WriteLine(count);  // 6
csharp

out は「戻り値をもう1つ増やす」ための仕組みです。メソッド内で必ず値を代入する必要があります。

public bool TryDivide(int a, int b, out int result)
{
    if (b == 0)
    {
        result = 0;
        return false;
    }
 
    result = a / b;
    return true;
}
 
if (TryDivide(10, 2, out var answer))
{
    Console.WriteLine(answer);  // 5
}
csharp
out は「成功したかどうかを戻り値で、結果を引数で返す」というC#の定番パターンです。

int.TryParse や Dictionary.TryGetValue がこの形をとっているのは、変換や検索の失敗を例外ではなく戻り値で表現するためです。

設計を考えるイメージ
引数の渡し方は「何を返したいか」から選びます

省略可能引数と名前付き引数

引数に既定値を与えると、呼び出し時に省略できます。

public void Log(string message, string level = "INFO", bool withTime = true)
{
    Console.WriteLine($"[{level}] {message}");
}
 
Log("起動しました");
Log("失敗しました", "ERROR");
Log("処理中", withTime: false);   // 名前付きで飛ばして指定
csharp

引数が多いメソッドでは、名前付き引数を使うと呼び出し側が読みやすくなります。Log("処理中", "INFO", false) より意図が伝わります。

可変長引数(params)

個数が決まらない引数は params で受け取ります。

public int Sum(params int[] numbers)
{
    int total = 0;
    foreach (var n in numbers) total += n;
    return total;
}
 
Sum(1, 2, 3);       // 6
Sum(1, 2, 3, 4, 5); // 15
csharp

params は引数リストの最後にしか置けません。

オーバーロード

同じ名前で、引数の型や個数が違うメソッドを複数定義できます。

public string Format(int value) => $"数値: {value}";
public string Format(string value) => $"文字列: {value}";
public string Format(DateTime value) => $"日時: {value:yyyy/MM/dd}";
csharp

呼び出し時の引数から、どれを使うかがコンパイル時に決まります。Console.WriteLine が文字列でも数値でもオブジェクトでも受け取れるのは、多数のオーバーロードが定義されているためです。

式形式メンバーとローカル関数

1行で書けるメソッドは => で簡潔に書けます。

public int Square(int n) => n * n;
csharp

メソッドの中だけで使う小さな処理は、ローカル関数として内側に定義できます。

public void Report(List<int> scores)
{
    Console.WriteLine($"平均: {Average()}");
 
    double Average() => scores.Count == 0 ? 0 : scores.Sum() / (double)scores.Count;
}
csharp

標準ライブラリの例:string.Join

シグネチャの読み方に慣れるため、よく使うメソッドを1つ見ておきます。

構文: string.Join(separator, values)

引数渡せるもの説明
separator(第1引数)文字列 / char各要素のあいだに挟む区切り文字
values(第2引数)配列 / IEnumerable<T>連結したい要素の集まり

戻り値: 連結した新しい文字列(要素が0個なら空文字列)

var names = new List<string> { "田中", "鈴木", "佐藤" };
Console.WriteLine(string.Join("、", names));  // 田中、鈴木、佐藤
csharp

よくあるハマりどころ

out 変数に代入し忘れる

out を付けた引数は、メソッドを抜けるまでに必ず代入しなければコンパイルエラーになります。早期 return する分岐でも忘れずに代入します。

引数を書き換えて呼び出し元が変わると思い込む

public void Reset(List<int> list)
{
    list = new List<int>();  // 呼び出し元は変わらない
}
csharp

新しいインスタンスを代入しても、呼び出し元の変数はもとのリストを指したままです。中身を空にしたいなら list.Clear() を呼びます。

先生先生

「中身を変える」のか「変数ごと差し替える」のかを区別すると、ref が必要な場面がはっきりするよ。

省略可能引数の既定値はライブラリ更新で反映されない

既定値は呼び出し側のコードにコンパイル時に埋め込まれます。ライブラリ側で既定値を変えても、再ビルドしない限り古い値のままです。公開ライブラリではオーバーロードを使うほうが安全です。

ちゃんと使うためのポイント

  • メソッド名はパスカルケース、引数はキャメルケース
  • 通常は値渡し。呼び出し元の変数を書き換えたいときだけ ref
  • 「成功可否+結果」を返したいときは out(TryParse 型のパターン)
  • 引数が多いときは名前付き引数で読みやすくする
  • 同じ役割で型違いの入口を作りたいときはオーバーロード
  • 1行で済む処理は => の式形式で書く

次の章では、メソッドとデータをまとめる入れ物であるクラスに進みます。C#のオブジェクト指向の中心となる部分です。

参考リンク