省略可能な引数はメソッドの宣言で既定値を持つので、呼び出し側はそれを省略できます。名前付き引数は、位置ではなく引数名で値を渡します。この2つを組み合わせると、大量のオーバーロードなしで1つのメソッドがさまざまな呼び出し方に対応できます。
出力:
to ana@mail.com: (no subject), retries 3
to ben@mail.com: Invoice #1042, retries 3
to cy@mail.com: (no subject) [URGENT], retries 3
to dev@mail.com: Build failed, retries 0
3つ目の呼び出しは subject を飛ばし、urgent を名前で指定しています。名前付き引数がなければ、3番目の位置に届くためだけに "(no subject)" をもう一度渡さなければなりません。最後の呼び出しはすべての引数を名前付きで、宣言とは異なる順序で渡していますが、これは正しい構文です。
既定値のルール
既定値は、コンパイラが計算できるものでなければなりません。
- 定数(
3、"INFO"、true、1.5m、constフィールド、enumのメンバー) - 参照型またはnull許容型に対する
null default(T)、または値型Tに対するnew T()
実行時に評価されるものはすべて拒否されます。典型的な例は日付です。
static void Schedule(string task, DateTime at = DateTime.Now) { }
// error CS1736: Default parameter value for 'at' must be a compile-time constant
標準的な回避策は、null を既定値とする null許容 の引数にして、本体で値を決める方法です。
出力:
backup at 2026-01-01 09:00, tags: 0
report at 2026-03-15 18:30, tags: 0
deploy at 2026-01-01 09:00, tags: 2
同じ方法はコレクションにも使えます。new List<string>() という既定値は定数ではないので、既定値を null にして中でリストを作ります。これで、Pythonのような可変の既定値が共有されるバグも避けられます。C#は定数しか許さないので、そもそもこのバグは起こりえません。
順序のルール
必須の引数が先、省略可能な引数がその後、(あれば)params 配列が最後です。
static void Log(string message, string level = "INFO", params string[] tags) { } // OK
static void Log(string level = "INFO", string message) { }
// error CS1737: Optional parameters must appear after all required parameters
ref と out の引数は省略可能にできません。
呼び出し側では、位置による引数が先頭から引数を埋めます。名前付き引数はその後に任意の順序で続けられます。C# 7.2以降は、名前付き引数を位置による引数の前に置くこともできますが、それは名前付き引数が本来の位置にある場合に限ります(SendEmail("a@b.c", subject: "Hi", true))。それより古いバージョンでは、名前付き引数はすべて最後に置かなければなりません。他の引数を名前で指定していても、必須の引数を省略するとコンパイルエラーになります。
読みやすさのための名前付き引数
名前付き引数は、省略可能な引数がなくても役に立ちます。true、false、null のリテラルや単なる数値は、呼び出し箇所では何も語りません。
ResizeImage(photo, 800, 600, true, false); // which is which?
ResizeImage(photo, width: 800, height: 600, keepAspect: true, upscale: false);
引数名を変更すると、名前を使っている呼び出し側にとって破壊的な変更になります。公開ライブラリでは覚えておく価値があります。
省略可能な引数とオーバーロード
C# 4より前は、同じ柔軟性を得るのに組み合わせごとのオーバーロードが必要でした。省略可能な引数はそれらを1つのメソッドにまとめます。
// overloads
static void Connect(string host) => Connect(host, 443);
static void Connect(string host, int port) => Connect(host, port, 30);
static void Connect(string host, int port, int timeoutSeconds) { /* ... */ }
// one method with optional parameters
static void Connect(string host, int port = 443, int timeoutSeconds = 30) { /* ... */ }
両方が存在する場合、オーバーロードの解決では、既定値を補う必要がない候補が優先されます。
出力:
Greet()
Greet(string) with Lena
Greet() は両方のメソッドに一致し、コンパイラは省略された省略可能な引数がないほうを選びます。同じ名前で2つの手法を混ぜると、たいてい呼び出し先を予測しにくい呼び出しになるので、メソッドごとにどちらか一方を選びます。
変種ごとに異なるコードや異なる引数の型が必要ならオーバーロードを、既定値だけが異なるなら省略可能な引数を選びます。
既定値は呼び出し側に埋め込まれる
既定値は実行時に参照されるわけではありません。呼び出し側のコードがコンパイルされるときに、コンパイラが既定値を各呼び出し箇所にコピーします。Connect("api.shop.com") は Connect("api.shop.com", 443, 30) にコンパイルされます。
これはライブラリにとって重要な結果をもたらします。パッケージのバージョン1が Connect(string host, int timeoutSeconds = 30) を提供し、バージョン2で既定値を 10 に変えたとします。バージョン1に対してコンパイルされたアプリケーションは、バージョン2のDLLに差し替えた後も、アプリケーション自体を再コンパイルするまで 30 を渡し続けます。既存の公開メソッドに新しい省略可能な引数を追加しても、すでにコンパイル済みの呼び出し側は壊れます。メソッドのシグネチャが変わったのに、呼び出し側は古いシグネチャを探し続けるからです(実行時の MissingMethodException)。
全体をまとめてコンパイルする1つのアプリケーションの中では、これが問題になることはありません。NuGetパッケージの公開APIでは、(既定値をライブラリの中に保つ)オーバーロードか、本体で値を決める null の既定値でこの問題を避けられます。
既定値と宣言された型
同じコンパイル時のルールにより、インターフェースとクラスの両方が既定値を宣言している場合、既定値はオブジェクトではなく、呼び出しに使う変数の型から決まります。
出力:
printing "report" x5
printing "report" x1
同じオブジェクトなのに、2つの異なる既定値になりました。インターフェースとその実装の間で既定値を同じにするか、1か所でだけ宣言します。
呼び出し元情報の属性
省略可能な引数は、System.Runtime.CompilerServices の呼び出し元情報の属性も支えています。コンパイラが呼び出し箇所の詳細でそれらを埋めます。
出力:
[Main:18] starting
[SaveOrder:13] order saved
ロギングライブラリはこの仕組みで、呼び出し側が入力しなくてもメッセージの出どころを記録し、INotifyPropertyChanged の実装はプロパティ名を取得します。[CallerFilePath] も同じようにソースファイルのパスを追加します。
よくある質問
C#で引数を省略可能にするには?
宣言で既定値を与えます:static void Log(string message, string level = "INFO")。呼び出し側は Log("started") とも Log("failed", "ERROR") とも書けます。省略可能な引数はすべての必須の引数の後に置く必要があり、既定値はコンパイル時定数でなければなりません。
C#の名前付き引数とは何ですか?
引数名を付けて渡す引数です:SendEmail(to: "ana@mail.com", urgent: true)。途中の省略可能な引数を飛ばしたり、任意の順序で引数を渡したりでき、true/false や数値のリテラルを渡す呼び出しがひと目で読めるようになります。
DateTime.Nowを引数の既定値に使えないのはなぜですか?
既定値はコンパイル時定数でなければならず、DateTime.Now は実行時に計算されるので、コンパイラはCS1736 Default parameter value for 'at' must be a compile-time constant を報告します。代わりにnull許容の引数を使います:DateTime? at = null とし、本体で DateTime time = at ?? DateTime.Now; と書きます。
C#では省略可能な引数とメソッドのオーバーロードのどちらを使うべきですか?
既定値だけが異なる場合は、省略可能な引数のほうが簡単です。異なる処理や型が必要な場合や、公開ライブラリではオーバーロードのほうが適しています。既定値はすべての呼び出し側にコンパイルされて埋め込まれ、後で変更しても、すでにコンパイル済みのコードには反映されないからです。
「Optional parameters must appear after all required parameters」とはどういう意味ですか?
エラーCS1737です。既定値を持つ引数の後に、既定値のない引数が続いています。必須の引数を先に移します:(bool urgent = false, string to) ではなく (string to, bool urgent = false) にします。省略可能な引数の後には、省略可能な引数か params 配列しか続けられません。