【C#】メソッド完全ガイド|引数の種類・戻り値・オーバーロード・式形体・ローカル関数・拡張メソッドまで

C# でコードを書くうえで欠かせない基礎が メソッド です。処理を一塊にまとめて名前を付けることで、コードの再利用性・可読性・保守性が大きく上がります。

本記事では基本的なメソッド定義から、引数の全種類(デフォルト値・名前付き・params・ref/out/in)、複数の戻り値の返し方、オーバーロード、式形体メソッド、ローカル関数、拡張メソッドまで体系的に解説します。

スポンサーリンク

メソッドの基本構文

メソッドの宣言は「アクセス修飾子 戻り値の型 メソッド名(引数リスト)」が基本形です。

メソッドの基本構文
// アクセス修飾子  戻り値の型  メソッド名   引数
public            int         Add         (int a, int b)
{
    return a + b; // 戻り値を return で返す
}

// 戻り値なし(void)
public void PrintMessage(string text)
{
    Console.WriteLine(text);
    // return; を省略できる(または途中で return して早期終了)
}
メソッドの呼び出し
class Calculator
{
    public int Add(int a, int b) => a + b;
    public void Print(string msg) => Console.WriteLine(msg);
}

class Program
{
    static void Main()
    {
        var calc = new Calculator();

        int result = calc.Add(3, 5);    // 戻り値を変数に代入
        Console.WriteLine(result);       // 8

        calc.Print("Hello");            // 戻り値なし(voidメソッド)
    }
}
アクセス修飾子の指定
メソッドにはアクセス修飾子を付けます。付けない場合はデフォルトで private になります。クラス外から呼び出せるようにするには public を明示してください。
public — クラス外からもアクセス可
private — クラス内のみ(デフォルト)
protected — 派生クラスからもアクセス可
internal — 同じアセンブリ内のみ

引数の種類

デフォルト値付き引数(省略可能引数)

引数にデフォルト値を設定しておくと、呼び出し時にその引数を省略できます。省略した場合はデフォルト値が使われます。

デフォルト値付き引数
// デフォルト値付き引数はリストの末尾に置く
static string Greet(string name, string greeting = "こんにちは")
{
    return $"{greeting}、{name}さん!";
}

Console.WriteLine(Greet("Taro"));               // こんにちは、Taroさん!
Console.WriteLine(Greet("Hanako", "おはよう"));  // おはよう、Hanakoさん!

名前付き引数(Named Arguments)

名前付き引数を使うと、引数名を明示して渡せます。順序を変えて渡すことも可能になり、引数が多いメソッドの呼び出しが読みやすくなります。

名前付き引数
static void CreateUser(string name, int age, string role = "User")
{
    Console.WriteLine($"名前:{name} 年齢:{age} ロール:{role}");
}

// 名前を指定して渡す(順序を変えてもOK)
CreateUser(age: 25, name: "Taro");
CreateUser(name: "Admin", age: 30, role: "Admin");

// 特に bool 引数や同型が並ぶメソッドで可読性が上がる
static void Send(bool isHtml, bool retry, int timeout) { /* ... */ }
Send(isHtml: true, retry: false, timeout: 30); // 意図が明確

params:可変長引数

params を使うと、同じ型の引数をいくつでも渡せるメソッドを定義できます。呼び出し側は配列を渡しても、カンマ区切りで直接渡してもどちらでも構いません。

params の使い方
// params は引数リストの最後にのみ配置できる
static int Sum(params int[] numbers)
{
    int total = 0;
    foreach (var n in numbers)
        total += n;
    return total;
}

Console.WriteLine(Sum(1, 2, 3));          // 6
Console.WriteLine(Sum(10, 20, 30, 40));   // 100
Console.WriteLine(Sum());                 // 0(0個でもOK)

int[] arr = { 5, 10, 15 };
Console.WriteLine(Sum(arr));              // 30(配列を直接渡してもOK)

ref・out・in 修飾子

通常、引数は値渡し(コピー)されます。参照渡しにしたいときは refoutin を使います。

修飾子 読み書き ルール 主な用途
ref 参照渡し(読み書き両方) 呼び出し前に初期化が必要 値を渡して結果も受け取る
out 参照渡し(書き込み専用) メソッド内で必ず代入が必要 複数の戻り値・TryParseパターン
in 参照渡し(読み取り専用) 変更不可・コピーコスト削減 大きな構造体を効率的に渡す
ref・out・in の使用例
// ref: 呼び出し元の変数を直接変更する
static void Double(ref int value)
{
    value *= 2; // 呼び出し元の変数が書き換わる
}

int x = 5;
Double(ref x);
Console.WriteLine(x); // 10

// out: 複数の結果を返す(TryParseパターン)
static bool TryDivide(int a, int b, out int result, out string error)
{
    if (b == 0) { result = 0; error = "ゼロ除算"; return false; }
    result = a / b;
    error  = "";
    return true;
}

if (TryDivide(10, 2, out int quotient, out string err))
    Console.WriteLine(quotient); // 5
else
    Console.WriteLine(err);

// C# 7以降: out変数をインラインで宣言できる
if (int.TryParse("42", out int parsed))
    Console.WriteLine(parsed); // 42

// in: 読み取り専用の参照渡し(大きな構造体に有効)
static void PrintInfo(in DateTime dt)
{
    Console.WriteLine(dt.Year); // 読み取りのみ可
    // dt = DateTime.Now;       // コンパイルエラー
}
refout は便利ですが使いすぎるとコードの可読性が落ちます。複数の戻り値を返したい場合は、まずタプル(ValueTuple)を検討しましょう。タプルの使い方と利点を参照してください。

複数の戻り値の返し方

C#でメソッドから複数の値を返す主な方法を比較します。

複数の戻り値を返す3つの方法
// 方法①: ValueTuple(C# 7以降・推奨)
static (int min, int max) MinMax(int[] arr)
{
    return (arr.Min(), arr.Max());
}

var (min, max) = MinMax(new[] { 3, 1, 4, 1, 5, 9, 2 });
Console.WriteLine($"最小:{min} 最大:{max}"); // 最小:1 最大:9

// 方法②: out パラメータ(TryParseパターンに向く)
static bool TryMinMax(int[] arr, out int min, out int max)
{
    if (arr.Length == 0) { min = max = 0; return false; }
    min = arr.Min(); max = arr.Max();
    return true;
}

// 方法③: 専用クラスやレコード型(複数箇所で使う場合)
record MinMaxResult(int Min, int Max);
static MinMaxResult GetMinMax(int[] arr) => new(arr.Min(), arr.Max());
一時的に複数の値を返すだけなら ValueTuple が最もシンプルです。同じ結果型を複数メソッドで使い回すなら record 型 が保守性に優れます。out パラメータは TryXxx パターン専用と考えるとコードが整理されます。

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

同じメソッド名で引数の型・個数が異なる複数バージョンを定義することを オーバーロード と言います。呼び出し時の引数によってどのバージョンを呼ぶか自動的に決まります。

オーバーロードの例
class Printer
{
    // 引数の型が異なる
    public void Print(int value)    => Console.WriteLine($"整数: {value}");
    public void Print(double value) => Console.WriteLine($"小数: {value}");
    public void Print(string value) => Console.WriteLine($"文字列: {value}");

    // 引数の個数が異なる
    public void Print(string label, int value)
        => Console.WriteLine($"{label}: {value}");
}

var p = new Printer();
p.Print(42);           // 整数: 42
p.Print(3.14);         // 小数: 3.14
p.Print("Hello");      // 文字列: Hello
p.Print("スコア", 95); // スコア: 95
戻り値の型だけが異なるオーバーロードは定義できません。コンパイラは引数で判断するため、戻り値だけでは区別できないからです。また、デフォルト値と組み合わせると「どのオーバーロードが呼ばれるか」が曖昧になることがあります。複雑な組み合わせは避け、意図を明確にしましょう。

式形体メソッド(Expression-bodied Members)

メソッドの本体が1行の式で書ける場合、=> を使ってよりコンパクトに記述できます。C# 6以降で使えます。

式形体メソッドの書き方
class MathHelper
{
    // 通常の書き方
    public int AddNormal(int a, int b)
    {
        return a + b;
    }

    // 式形体(=> を使ってコンパクトに)
    public int Add(int a, int b) => a + b;
    public double Square(double x) => x * x;
    public string Greet(string name) => $"Hello, {name}!";

    // void メソッドにも使える
    public void Log(string msg) => Console.WriteLine(msg);

    // プロパティにも使える
    public string FullName => $"{FirstName} {LastName}";
    public string FirstName { get; set; } = "";
    public string LastName  { get; set; } = "";
}
式形体は「本体が1式で収まる」場合に限り使います。複数の処理・条件分岐・例外処理が入る場合は通常のブロック形式の方が読みやすくなります。無理に1行に収めようとするのは禁物です。

ローカル関数(Local Functions)

ローカル関数はメソッドの内部に定義できる関数です(C# 7以降)。外部からは見えないため、そのメソッドだけで使うサブ処理をスコープを限定して整理できます。

ローカル関数の定義と使い方
static int Factorial(int n)
{
    // 入力チェック
    if (n < 0) throw new ArgumentException("負の値は不可");

    return Calc(n); // ローカル関数を呼ぶ

    // ローカル関数: メソッド内部にだけ存在する
    static int Calc(int x) => x <= 1 ? 1 : x * Calc(x - 1);
}

Console.WriteLine(Factorial(5));  // 120
Console.WriteLine(Factorial(10)); // 3628800
外部スコープの変数をキャプチャする
static IEnumerable<int> GetEvenNumbers(IEnumerable<int> source)
{
    // ローカル関数は外のスコープの変数を参照できる
    bool IsEven(int n) => n % 2 == 0;

    foreach (var n in source)
        if (IsEven(n))
            yield return n;
}
種類 スコープ 特徴 向いている用途
ローカル関数 メソッド内のみ 型安全・外スコープ参照可 再帰・複雑なサブ処理の分離
ラムダ式 その場限り・デリゲート型 コンパクト・キャプチャに注意 LINQのWhere/Select・コールバック
ラムダ式との使い分けはラムダと匿名メソッドの違いと使い方で詳しく解説しています。

拡張メソッド(Extension Methods)

拡張メソッドを使うと、既存のクラスを継承・変更することなく新しいメソッドを追加できます(C# 3.0以降)。this キーワードを第1引数に付けた static メソッドとして定義します。

拡張メソッドの定義と使い方
// 必ず static クラス内の static メソッドとして定義する
public static class StringExtensions
{
    // string に IsNullOrEmpty の逆("空でない")を追加
    public static bool HasValue(this string? s)
        => !string.IsNullOrWhiteSpace(s);

    // string を int に変換(失敗時は null)
    public static int? ToIntOrNull(this string s)
        => int.TryParse(s, out int n) ? n : null;

    // 先頭文字を大文字にする
    public static string Capitalize(this string s)
        => string.IsNullOrEmpty(s) ? s : char.ToUpper(s[0]) + s[1..];
}

// 使い方: あたかも string の組み込みメソッドのように呼べる
string name = "taro";
Console.WriteLine(name.HasValue());    // true
Console.WriteLine(name.Capitalize()); // Taro

string num = "42";
int? parsed = num.ToIntOrNull();
Console.WriteLine(parsed); // 42
拡張メソッドはあくまで糖衣構文です。インスタンスのフィールドや private メンバーにはアクセスできません。また、同名のインスタンスメソッドが存在する場合はインスタンスメソッドが優先されます。LINQ のメソッド群(Where/Select 等)はすべて IEnumerable<T> に対する拡張メソッドとして実装されています。

再帰メソッド

メソッドが自分自身を呼び出す構造を 再帰 と言います。木構造の探索・分割統治アルゴリズム・数学的な定義の実装などに使われます。

再帰メソッドの例(フィボナッチ数列)
// 単純な再帰(大きい n では指数的に遅くなるので注意)
static long Fibonacci(int n)
{
    if (n <= 1) return n; // 基底条件(再帰の終了)
    return Fibonacci(n - 1) + Fibonacci(n - 2);
}

// メモ化で高速化したバージョン
static Dictionary<int, long> memo = new();
static long FibMemo(int n)
{
    if (n <= 1) return n;
    if (memo.TryGetValue(n, out long cached)) return cached;
    return memo[n] = FibMemo(n - 1) + FibMemo(n - 2);
}

Console.WriteLine(Fibonacci(10));  // 55
Console.WriteLine(FibMemo(50));    // 12586269025
再帰の深さが深くなりすぎると StackOverflowException が発生します。基底条件(再帰を止める条件)を必ず設けてください。深い再帰が必要な場合はループに書き換えるか、末尾再帰の最適化を検討します。

よくある落とし穴と注意点

void メソッドに return 値を書く

void メソッドでは値を返せません。途中でメソッドを終了したい場合は引数なしの return; を使います。値を返そうとすると「void メソッドで return に値を指定することはできません」というコンパイルエラーになります。

ref・out 引数は呼び出し側でも明示が必要

refout を使うメソッドを呼び出す際は、呼び出し側でも ref / out キーワードを書く必要があります。省略するとコンパイルエラーになります。これは意図せず参照渡しのメソッドを呼んでしまうことを防ぐ設計です。

デフォルト値付き引数と オーバーロードの混在

デフォルト値付き引数とオーバーロードを同時に定義すると、「どちらが呼ばれるか」が曖昧になる場合があります。たとえば Greet(string name, string title = "")Greet(string name) を両方定義すると Greet("Taro") の呼び出しがコンパイルエラーになります。片方に統一するか、呼び出しを名前付き引数で明示しましょう。

拡張メソッドが認識されない

拡張メソッドを使うには、定義されている名前空間を using で取り込む必要があります。LINQ メソッドを使うときに using System.Linq; が必要なのと同じ理由です。拡張メソッドを含むクラスが別のアセンブリにある場合は、参照の追加も必要です。

ローカル関数でラムダのつもりでデリゲートに代入できない

ローカル関数はデリゲート型の変数にそのまま代入することはできません(Func<int,int> fn = LocalFunc; のように変換は可能)。「デリゲートやイベントのハンドラとして渡したい」用途にはラムダ式の方が適しています。

よくある質問

Qstaticメソッドとインスタンスメソッドはどう使い分けますか?
Aインスタンスの状態(フィールド・プロパティ)を使う処理はインスタンスメソッド、オブジェクトの状態に依存しない処理(変換・計算・ユーティリティ)は static メソッドが向いています。詳しくはstaticメンバーの使い方と設計を参照してください。
Qメソッドは何個まで引数を持てますか?
A仕様上の上限は 65535 個ですが、実務では引数が 3〜4 個を超えると読みにくくなります。引数が多い場合は専用のパラメータオブジェクト(クラスや record)にまとめてしまうのがベストプラクティスです。
Q式形体メソッド(=>)と通常のブロック形式はどちらを使うべきですか?
A1つの式で完結する処理は式形体、複数ステップ・条件分岐・例外処理を含む場合はブロック形式を使います。「1行に書けるから」という理由だけで式形体を使うのは避け、読みやすさを基準に判断しましょう。
Q拡張メソッドはどんな型にも追加できますか?
Aインターフェース・sealed クラス・string・int など、基本的にどの型にも拡張メソッドを追加できます。ただし static クラスには拡張メソッドを追加できません。また既存の型に追加できるのは「新しいメソッド」だけで、フィールドや自動プロパティは追加できません。
Qローカル関数とラムダはパフォーマンスに差がありますか?
Aローカル関数(特に static ローカル関数)はコンパイル時に通常のメソッドとして最適化されるため、外スコープのキャプチャが発生しません。一方、ラムダは変数をキャプチャする場合にクロージャオブジェクトが生成されヒープアロケーションが起きます。ホットパスでは static ローカル関数が有利ですが、通常のコードで気にする必要はありません。

まとめ

機能 書き方・キーワード 主な用途・ポイント
基本メソッド 戻り値型 名前(引数) { } 処理の単位をまとめて再利用可能にする
デフォルト値引数 int x = 0 呼び出し側の省略を可能にする。末尾に置く
名前付き引数 Method(name: "A") 引数の意図を明示・順序変更可
params params T[] args 可変長引数。リストの最後にのみ配置可
ref / out / in ref / out / in 参照渡し。out は TryXxx パターンに最適
オーバーロード 同名・異なる引数シグネチャ 型・個数で自動選択。戻り値の型だけは不可
式形体 => 式 1行で完結するメソッドをコンパクトに書く
ローカル関数 メソッド内部に 戻り値 名前() そのメソッド専用のサブ処理を分離する
拡張メソッド static T Method(this T obj) 既存クラスを変えずにメソッドを追加
再帰 自己呼び出し+基底条件 木構造・数学的定義。深すぎるとSOE

非同期メソッド(async/awaitTask<T>)での戻り値の扱いは非同期メソッドで戻り値を処理する方法を参照してください。