C#において、メソッドを呼び出す際に引数の数を固定せず、状況に応じて柔軟に値を渡したい場面があります。
例えば、複数の数値を合計するメソッドや、可変個のメッセージを整形して出力するログ機能などが挙げられます。
こうしたニーズに応えるのがparamsキーワード(可変長引数)です。
これまでのC#では、paramsは配列型に限定されていましたが、最新のC# 13(.NET 9)では大幅な強化が行われ、配列以外のコレクション型やSpanなども扱えるようになりました。
本記事では、基本的な使い方から最新の進化、そしてパフォーマンスを意識した実践的な活用術まで、余すことなく解説します。
params(可変長引数)の基本概念
paramsは、呼び出し側で任意の数の引数をカンマ区切りで渡せるようにするための修飾子です。
メソッド定義側では一つの配列(または対応する型)として受け取りますが、利用側は配列を明示的に作成する必要がないため、コードが非常にシンプルになります。

この仕組みにより、開発者は「1つの引数を渡す場合」「3つの引数を渡す場合」「何も渡さない場合」といったパターンごとにオーバーロードを記述する手間から解放されます。
paramsの基本的な構文と実装
まずは、最も一般的である配列を使用したparamsの実装方法を見ていきましょう。
using System;
class Program
{
static void Main()
{
// 引数を3つ渡す
PrintMessages("こんにちは", "C#の世界へ", "ようこそ!");
// 引数を1つだけ渡す
PrintMessages("ハロー!");
// 引数を渡さないことも可能(空の配列として扱われる)
PrintMessages();
// 配列を直接渡すことも可能
string\[\] array = { "配列", "として", "渡す" };
PrintMessages(array);
}
// paramsキーワードを使用して可変長引数を定義
static void PrintMessages(params string\[\] messages)
{
if (messages.Length == 0)
{
Console.WriteLine("メッセージはありません。");
return;
}
foreach (var msg in messages)
{
Console.WriteLine($"- {msg}");
}
}
}
- こんにちは
- C#の世界へ
- ようこそ!
- ハロー!
メッセージはありません。
- 配列
- として
- 渡す
上記のサンプルコードでは、PrintMessagesメソッドにparams string[]を定義しています。
呼び出し側では文字列を好きなだけ並べるだけで、自動的に配列としてラップされてメソッド内部に渡されます。
また、既に存在する配列をそのまま渡すこともできるため、非常に汎用性が高いのが特徴です。
paramsを使用する際の重要なルール
paramsは非常に便利ですが、C#の文法としていくつかの制約が存在します。
これらを守らないとコンパイルエラーになるため、正しく理解しておく必要があります。
1. 引数リストの最後に配置する
params修飾子を指定した引数は、メソッドの引数リストの最後に配置しなければなりません。
これは、コンパイラが「どこからどこまでが可変長引数なのか」を判別できるようにするためです。
// 正しい例
void CorrectMethod(string prefix, params int\[\] values) { }
// エラーになる例(paramsの後に引数がある)
// void ErrorMethod(params int\[\] values, string suffix) { }
2. paramsは一つしか指定できない
一つのメソッドに対して、paramsを指定できる引数は一つだけです。
複数の可変長引数を持つことはできません。
// エラーになる例
// void MultiParams(params int\[\] nums, params string\[\] texts) { }
3. 一次元配列(またはサポートされた型)であること
従来のC#では一次元配列のみが許可されていました。
多次元配列(int[,]など)にparamsを付けることはできません。
なお、最新のC#では配列以外の型もサポートされていますが、それについては後述します。
C# 13における最新の進化:コレクション型のサポート
C# 13(.NET 9環境)において、paramsは劇的な進化を遂げました。
これまでは「配列」しか指定できませんでしたが、あらゆるコレクション型やSpan型に対してparamsを付与できるようになりました。

サポートされるようになった主な型
新しくparamsで利用可能になった型には、以下のようなものがあります。
| カテゴリ | 具体的な型 |
|---|---|
| コレクション型** | IEnumerable<T>, IList<T>, ICollection<T>, List<T> |
| 低レイヤ/パフォーマンス向上の型** | Span<T>, ReadOnlySpan<T> |
これにより、インターフェースを介した柔軟な設計や、メモリ割り当て(アロケーション)を抑えた高速なプログラムが記述しやすくなりました。
C# 13での実装例
最新の仕様を利用したコードを見てみましょう。
特にReadOnlySpan<T>を利用した例は、パフォーマンス面で大きなメリットがあります。
using System;
using System.Collections.Generic;
class ModernCsharp
{
static void Main()
{
// IEnumerable<int>として受け取る
ProcessNumbers(1, 2, 3, 4, 5);
// ReadOnlySpan<char>として受け取る(効率的)
DisplayChars('A', 'B', 'C');
}
// C# 13ではIEnumerableも可能
static void ProcessNumbers(params IEnumerable<int> numbers)
{
foreach (var n in numbers)
{
Console.Write($"{n} ");
}
Console.WriteLine();
}
// パフォーマンス重視の場合、ReadOnlySpanを使用
static void DisplayChars(params ReadOnlySpan<char> characters)
{
foreach (var c in characters)
{
Console.Write($"{c} ");
}
Console.WriteLine();
}
}
1 2 3 4 5
A B C
ReadOnlySpan<T>をparamsとして使用すると、コンパイラは可能な限りスタック領域を利用したり、既存のデータを再利用したりするため、ヒープメモリへの割り当てを減らすことができます。
これは高頻度で呼ばれるメソッドにおいて、GC(ガベージコレクション)の負荷を軽減する非常に強力な武器となります。
paramsの動作原理とパフォーマンスへの影響
paramsを使うと、内部ではどのような処理が行われているのでしょうか。
パフォーマンスの観点から注意点を整理します。
配列生成のコスト
従来のparams T[]を使用した場合、メソッドを呼び出すたびに新しい配列インスタンスがヒープ上に作成されます。
たとえ引数が0個であっても、空の配列が生成される場合があります。
// 呼び出し側
DoSomething(1, 2, 3);
// 実際には内部で以下のような処理が行われている
// int\[\] temp = new int\[\] { 1, 2, 3 };
// DoSomething(temp);
ループの中で何度もparams付きのメソッドを呼び出すと、大量の短期的なオブジェクトが生成され、メモリ圧迫の原因になることがあります。
これを回避するためには、C# 13で導入されたReadOnlySpan<T>を活用するか、頻繁に利用される引数の数(例えば引数0〜2個など)に対して個別のオーバーロードを用意しておくのが定石です。

メソッドオーバーロードの優先順位
paramsを持つメソッドと、通常の引数を持つメソッドが混在する場合、C#コンパイラは「より具体的な一致」を優先します。
static void Test(int a) { Console.WriteLine("通常メソッド"); }
static void Test(params int\[\] a) { Console.WriteLine("paramsメソッド"); }
static void Main()
{
Test(10); // どちらが呼ばれるか?
}
この場合、結果は「通常メソッド」になります。
コンパイラは、paramsによる配列変換を必要としないメソッドの方を「型の一致度が高い」と判断するためです。
この挙動を理解しておけば、特定の引数パターンだけパフォーマンスを最適化する(配列生成を避ける)といった設計が可能になります。
実践的な活用シーン
paramsを効果的に活用できる具体的なケースをいくつか紹介します。
1. 汎用的なロギング・フォーマット関数
文字列の構築を柔軟に行いたい場合に最適です。
public void Log(string level, params object\[\] args)
{
string message = string.Format("\[{0}\] {1}", level, string.Join(", ", args));
Console.WriteLine(message);
}
// 使用例
Log("INFO", "サーバー起動", 8080, "正常");
2. 計算ユーティリティ
複数の値を一度に処理する計算ロジックを簡潔に書けます。
public static double CalculateAverage(params double\[\] values)
{
if (values.Length == 0) return 0;
double sum = 0;
foreach (var v in values) sum += v;
return sum / values.Length;
}
3. コレクションへの動的な追加
特定の条件に合致する要素を、一度に複数追加するようなAPI設計に役立ちます。
public class MyFolder
{
private List<string> files = new();
public void AddFiles(params string\[\] newFiles)
{
files.AddRange(newFiles);
}
}
まとめ
paramsキーワードは、C#プログラムの柔軟性と可読性を向上させる非常に強力な機能です。
呼び出し側のコードをスッキリさせるだけでなく、最新のC# 13ではSpan<T>や各種コレクション型への対応により、「使いやすさ」と「パフォーマンス」の両立が可能になりました。
基本的なルールである「引数リストの最後にする」「一つだけ定義する」という点を押さえつつ、メモリ効率が求められる場面ではReadOnlySpan<T>を、汎用性が求められる場面では配列やIEnumerable<T>を選択するといった、状況に応じた使い分けが重要です。
この記事を参考に、ぜひ最新のparamsを自身のプロジェクトに取り入れてみてください。
