++C++; // 未確認飛行 C

オプション引数・名前付き引数

目次

キーワード

概要

Ver. 4.0

C# 4.0 でオプション引数と名前付き引数が追加されました。

ポイント
  • オプション引数とデフォルト値: int Sum(int x = 0, int y = 0) { return x + y; } とか書けるようになった
  • オプション引数の省略: Sum(); Sum(1); Sum(1, 2);
  • 名前付き引数: Sum(x: 1, y: 2); Sum(y:1, x: 2); Sum(y: 1);

オプション引数

オプション引数は C++ にもある機能ですね。 これは、メソッドのオーバーロードで似たようなことが可能なので、 今まで C# では敬遠し続けてきたようです。

まず、C++ 同様、 以下のようにデフォルト値を持ったメソッドを定義します。

static int Sum(int x = 0, int y = 0, int z = 0)
{
  return x + y + z;
}

すると、以下のように、引数の一部もしくは全てを省略可能になります。 省略可能ということで、オプション引数(optional parameter)と呼びます。

int s1 = Sum();     // Sum(0, 0, 0); と同じ意味。
int s2 = Sum(1);    // Sum(1, 0, 0); と同じ意味。
int s3 = Sum(1, 2); // Sum(1, 2, 0); と同じ意味。

この記法で省略可能になるのは、後ろの引数のみです。 この例でいうところの、z だけをオプションにして x と y だけを省略することはできません。 定義側でも、以下のようなコードはコンパイルエラーになります。 (z のところで「オプション引数の後ろに必須引数を置いちゃダメ」みたいなエラーが出ます。)

static int Sum(int x = 0, int y = 0,
  int z) // ここでコンパイルエラー起こす
{
  return x + y + z;
}

ただし、オプション引数の後ろに params( 「可変長引数」 参照)を続けることは可能です。

static int Sum(int x, int y, int z = 0, params int[] rest)
{
    var sum = x + y + z;
    foreach (var v in rest) sum += v;
    return sum;
}

オプション引数や可変長引数を使った場合のオーバーロードの優先順位ですが、 オプションなし > オプションあり > 可変長引数 の順で優先されます。

static void Main(string[] args)
{
    Sum(1);
    Sum(1, 2);
    Sum(1, 2, 3);
    Sum(1, 2, 3, 4);
}

static int Sum(int x)
{
    Console.WriteLine("Sum(x)");
    return x;
}

static int Sum(int x, int y = 0, int z = 0) // 引数2つ以上でないと呼ばれない
{
    Console.WriteLine("Sum(x, y, z)");
    return x + y + z;
}

static int Sum(params int[] rest) // 引数4つ以上でないと呼ばれない
{
    Console.WriteLine("Sum(rest)");
    var sum = 0;
    foreach (var v in rest) sum += v;
    return sum;
}
Sum(x)
Sum(x, y, z)
Sum(x, y, z)
Sum(rest)

名前付き引数

で、もう1つ、 こちらも VB には昔からある機能なんですが、 名前付き引数(named parameter)が使えるようになりました。

先ほど定義したデフォルト引数付きのメソッドを、以下のような構文で呼び出せます。

int s1 = Sum(x: 1, y: 2, z: 3); // Sum(1, 2, 3); と同じ意味。
int s2 = Sum(y: 1, z: 2, x: 3); // Sum(3, 1, 2); と同じ意味。
int s3 = Sum(y: 1);             // Sum(0, 1, 0); と同じ意味。

名前付き引数の場合、引数の順序は自由に書けます。 また、任意の箇所を省略可能になります。

1つ気をつけないと行けないのは、引数の名前を指定するのに = じゃなくて : を使うところです。 C# の場合、以下のような構文が許されているので、間違えて = と書いてしまわないよう気をつけましょう。

static void Main(string[] args)
{
    int x = 0;
    Console.WriteLine(Square(x = 2));
    // ↑これは↓と同じ意味。
    // x = 2;
    // Console.WriteLine(Square(x));
}

static int Square(int x)
{
    return x * x;
}

内部実装

オプション引数(メソッド定義側)

オプション引数の仕組みは、今までの VB.NET と同じ実装方法で実現されていて、 実体は Optional 属性と DefaultParameterValue 属性になっています。 例えば、以下のようなコードを書くと、

static int Sum(int x = 0, int y = 0, int z = 0)
{
    return x + y + z;
}

以下のようなコードと同じコンパイル結果になります。 (Optional, DefaultParameterValue はいずれも System.Runtime.InteropServices 名前空間内に定義されている属性です。)

static int Sum(
    [Optional, DefaultParameterValue(0)] int x,
    [Optional, DefaultParameterValue(0)] int y,
    [Optional, DefaultParameterValue(0)] int z)
{
    return ((x + y) + z);
}
名前付き引数(メソッド定義側)

C# (や、VB など、.NET 上の言語)では、元々、コンパイル結果にメソッドの引数名に関する情報が残っています。 名前付き引数はこの情報に基づいて実装されています。

メソッド呼び出し側

オプション引数や名前付き引数を使ったメソッド呼び出しでは、 コンパイル時に値が全て展開された状態になります。

例えば、先ほどの Sum メソッドに対して、以下のようなコードは、

Sum();
Sum(1);
Sum(1, 2);
Sum(x: 1, y: 2, z: 3);
Sum(y: 1, z: 2, x: 3);

以下のようなコードと完全に同じコンパイル結果になります。

Sum(0, 0, 0); // 元々 0 を渡していたのか、オプション引数で 0 になったのかはわからない
Sum(1, 0, 0);
Sum(1, 2, 0);
Sum(1, 2, 3); // x, y, z 等の引数名に関する情報は残らない
Sum(3, 1, 2);

余談: なんでいまさら?

なんで今まで実装されなかったの?(その1)

デフォルト引数は C++ にもあるし、 VB はデフォルト引数・名前付き引数ともにかなり前から実装していました。 C# でも、かなり初期の頃からずっと、デフォルト引数が欲しいという要望はたびたび出ていました。 にもかかわらず、C# 4.0 でようやくの実装になります。

というのも、デフォルト引数にはちょっとした問題もあったりするからです。 (名前付き引数はデフォルト引数が前提の機能なので、デフォルト引数を実装しない = 名前付き引数もない。)

何かというと、デフォルト引数のデフォルト値を変更した場合、メソッド定義側だけじゃなく、 メソッド呼び出し側も再コンパイルしないと値が狂う。 ( 「const メンバー」 でちょっと触れていますが、public な定数でも同様の問題が発生します。)

例えば、ライブラリ内で以下のようなコードを書いたとして、

static int Sum(int x = 0, int y = 0, int z = 0)
{
    return x + y + z;
}

このライブラリを使う以下のようなコードを書いたとしてます。

Sum();

この Sum()Sum(0, 0, 0) と解釈されます。

この後、ライブラリを以下のように更新したとします。

static int Sum(int x = 1, int y = 2, int z = 3)
{
    return x + y + z;
}

当然、Sum() の部分は Sum(1, 2, 3) になって欲しいわけですが、 利用側を再コンパイルするまで、Sum(0, 0, 0) のままになります。 すなわち、「ライブラリだけコンパイルしなおして再配布」とかやろうとすると問題を起こす可能性があります。

なので、C# では今まで、デフォルト引数を導入する代わりに、 メソッドのオーバーロードを使った以下のような実装方法を推奨していました。 この場合は、利用側の再コンパイルは必要なくなります。

static int Sum(int x, int y, int z)
{
    return x + y + z;
}
static int Sum()
{
    return Sum(0, 0, 0);
}
なんで今まで実装されなかったの?(その2)

もう1個、名前付き引数に関しても、少々気をつけないといけない点があります。

メソッドの定義側で引数名を変更した場合、 利用側も、名前付き引数構文で呼び出ししている場合には修正が必要になります。

例のごとく、以下のような Sum メソッドがあって、

static int Sum(int x, int y, int z)
{
    return x + y + z;
}

これを以下のような名前付き引数を使って呼び出しているとします。

Sum(x: 0, y: 1, z: 2);

この時、Sum の定義側を以下のように変更すると、

static int Sum(int a, int b, int c)
{
    return a + b + c;
}

呼び出している側で、「そんな名前の引数はないよ」というエラーになります。

まあ、引数名は「表に出ているもの」(ドキュメントコメント等にも残る情報。誰からでも見えている。)なので、 元々そんなに軽々しく変更するものではないんですが、 名前付き引数を使うなら特に注意が必要です。

じゃあ、なんでいまさら実装されたの?

デフォルト引数の問題はあるものの、名前付き引数は非常に便利です。

今まで、C# から Excel の機能を(COM 経由で)呼び出そうとすると、以下のような悲惨なコードになることがありました。

Workbook workbook = excelApp.Workbooks.Open(
    "sample.xsl", Type.Missing, true, Type.Missing, Type.Missing,
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);

Type.Missing というのは、 オプション引数をサポートしていない言語からオプション引数を利用するための苦肉の策です。

(ちなみに、 この馬鹿みたいにいっぱいある引数は、どういうモードでワークブックを開くかです。 例えば、ワークブックを読み取り専用で開いたりとかを指定するためにある。)

で、これが C# 4.0 なら以下のようにシンプルに書けるようになります。

Workbook workbook = excelApp.Workbooks.Open("sample.xsl", ReadOnly: true);

.NET Framework 4.0 と C# 4.0 の掲げる目標の1つに、COM 相互運用の強化(要するに、この Excel 呼び出しとかをもっと簡単にする)というのがあって、 C# 4.0 でオプション引数・名前付き引数サポートが追加されたのも、 一番大きな理由はこの COM 相互運用のためではないかと。

[お問い合わせ](q)   ぷちカンパ