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 は引数リストの最後にのみ配置できる
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 修飾子
通常、引数は値渡し(コピー)されます。参照渡しにしたいときは ref・out・in を使います。
| 修飾子 | 読み書き | ルール | 主な用途 |
|---|---|---|---|
ref |
参照渡し(読み書き両方) | 呼び出し前に初期化が必要 | 値を渡して結果も受け取る |
out |
参照渡し(書き込み専用) | メソッド内で必ず代入が必要 | 複数の戻り値・TryParseパターン |
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; // コンパイルエラー
}
複数の戻り値の返し方
C#でメソッドから複数の値を返す主な方法を比較します。
// 方法①: 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());
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; } = "";
}
ローカル関数(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 引数は呼び出し側でも明示が必要
ref や out を使うメソッドを呼び出す際は、呼び出し側でも ref / out キーワードを書く必要があります。省略するとコンパイルエラーになります。これは意図せず参照渡しのメソッドを呼んでしまうことを防ぐ設計です。
デフォルト値付き引数と オーバーロードの混在
デフォルト値付き引数とオーバーロードを同時に定義すると、「どちらが呼ばれるか」が曖昧になる場合があります。たとえば Greet(string name, string title = "") と Greet(string name) を両方定義すると Greet("Taro") の呼び出しがコンパイルエラーになります。片方に統一するか、呼び出しを名前付き引数で明示しましょう。
拡張メソッドが認識されない
拡張メソッドを使うには、定義されている名前空間を using で取り込む必要があります。LINQ メソッドを使うときに using System.Linq; が必要なのと同じ理由です。拡張メソッドを含むクラスが別のアセンブリにある場合は、参照の追加も必要です。
ローカル関数でラムダのつもりでデリゲートに代入できない
ローカル関数はデリゲート型の変数にそのまま代入することはできません(Func<int,int> fn = LocalFunc; のように変換は可能)。「デリゲートやイベントのハンドラとして渡したい」用途にはラムダ式の方が適しています。
よくある質問
static メソッドが向いています。詳しくはstaticメンバーの使い方と設計を参照してください。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/await と Task<T>)での戻り値の扱いは非同期メソッドで戻り値を処理する方法を参照してください。
