C Sharpの基礎 - 可変長引数
概要
C#では、params
キーワードを使用することで、メソッドの引数の数を可変にすることができる。
// 定義側の例
int Sum(params int[] args)
{
// 処理
}
// 利用側の例
Sum(1, 2, 3, 4, 5); // Sum(new int[] { 1, 2, 3, 4, 5 }); と同じ意味
paramsキーワード
以下の2つの例は、可変個の整数のうち最大の整数を求めるメソッドである。
可変長引数を使用しない場合
可変長引数を使用せずにメソッドを実装すると、以下のようになる。
以下の方法では、値を配列に格納してからMaxメソッドを呼び出すという操作が必要になる。
Maxメソッドを呼び出すたびに一時的に配列を作成して、値を格納するという作業を行うのは面倒である。
using System;
class ParamsTest
{
static void Main()
{
int a = 314, b = 159, c = 265, d = 358, e = 979;
int[] tmp = new int[]{a, b, c, d, e};
int max = Max(tmp);
Console.Write("{0}\n", max);
}
static int Max(int[] a)
{
int max = a[0];
for(int i=1; i<a.Length; ++i)
{
if(max < a[i])
{
max = a[i];
}
}
return max;
}
}
可変長引数を使用する場合
C#では、params
キーワードを使用して、可変個の引数を取るメソッドを定義することができる。
以下の例では、上記の例をparamsキーワードを使用して書き直している。
using System;
class ParamsTest
{
static void Main()
{
int a = 314, b = 159, c = 265, d = 358, e = 979;
int max = Max(a, b, c, d, e); // 自動的に配列を作成して値を格納する
Console.Write("{0}\n", max);
}
static int Max(params int[] a)
{
int max = a[0];
for(int i=1; i<a.Length; ++i)
{
if(max < a[i])
{
max = a[i];
}
}
return max;
}
}
メソッド定義側の変更点は、Maxメソッドの引数であるint[] a
の前にparams
キーワードを記述しただけである。
呼び出し側では、手動で配列を用意して値を格納しなくとも、可変個の引数を与えてメソッドを呼び出すことができる。
サンプルコード
Console.Writeメソッド
は、可変長引数の機構を使用している。
以下の例では、paramsキーワードを使用して、Console.Writeメソッドのようなものを作成している。
using System;
class TestParams
{
static void Main(string[] args)
{
double x = 3.14;
int n = 99;
string s = "test string";
bool b = true;
Write("x = {0}, n = {1}, s = {2}, b = {3}\n", x, n, s, b);
}
/// <summary>
/// Console.Writeのようなもの
/// {0:d5} のような書式指定はできない
/// </summary>
/// <param name="format">書式指定文字列</param>
/// <param name="args">format を使用して書き込むオブジェクトの配列</param>
static void Write(string format, params object[] args)
{
for(int i = 0; i < args.Length; i++)
{
format = format.Replace("{" + i.ToString() + "}", args[i].ToString());
}
Console.Write(format);
}
}
// 出力
// x = 3.14, n = 99, s = test string, b = True
params IEnumerable
現行のC#では、paramsキーワードを使用できるものは配列だけである。
IEnuemrable<T>インターフェイス(System.Collections.Generic名前空間)を使用した可変長引数を記述したいという要望は多いが、
現在まで実装に至っていない。
これは、具体的にどのような型を作成して渡すことがよいかで揉めている。
配列の場合は配列が具象型であるため、配列以外を作成する選択肢は無いが、IEnumerable<T>の場合は型の種類が選択できる。
現在、配列よりも都合がよい型を採用できないか(値型にしてヒープ確保を避ける、immutableな型にして安全性を上げる等)が検討されている。
// コンパイル可能
static int Sum(params int[] source)
{
var sum = 0;
foreach (var x in source)
{
sum += x;
}
return sum;
}
// コンパイルエラー
static int Sum(params IEnumerable<int> source)
{
var sum = 0;
foreach (var x in source)
{
sum += x;
return sum;
}
}
可変長引数を引数無しで呼ぶ
可変長引数があるメソッドは、引数無しで呼ぶこともできる。
この場合、呼び出されたメソッドには、空配列(長さ0の配列)が渡される。
using System;
class Program
{
static void Main(string[] args)
{
var x = Sum();
Console.WriteLine(x); // 0
}
static int Sum(params int[] source)
{
// 引数無しで呼ばれた場合、配列sourceには空配列が代入される(配列sourceは、nullではないことに注意すること)
var sum = 0;
foreach (var x in source)
{
sum += x;
}
return sum;
}
}
上記において、空配列の生成方法は、.NET Frameworkのバージョンによって変わる。
.NET Framework 4.6以降 / .NET Coreでは、Array.Empty
という空配列を作成するためのメソッドがあるため、このメソッドが呼ばれる。
.NET Framework 4.5以前では、new T[0]
で空配列が生成される。
つまり、上記のサンプルコードにおいて、var x = Sum()
は、.NET Framework 4.5以前であれば、以下のように解釈される。
// .NET Framework 4.5以前
var x = Sum(new int[0]);
// .NET Framework 4.6以降
var x = Sum(Array.Empty<int>());
2つの方法が存在する理由は、Array.Emptyメソッドを使用する方がパフォーマンスが良いからである。
new int[0]の場合、メソッドの呼び出しのために配列のインスタンスが生成されるが、
Array.Emptyメソッドの場合、最初に生成した1つのインスタンスをキャッシュして使いまわす。
これは、破壊的変更なので注意すること。
例えば、以下のサンプルコードの挙動は.NET Frameworkのバージョンによって変わる。
using System;
class Program
{
static void Main(string[] args)
{
var x = IsCached();
Console.WriteLine(x);
var y = IsCached();
Console.WriteLine(y); // ターゲットによって結果が変わる
}
static int[] prev;
static bool IsCached(params int[] source)
{
// .NET Framework 4.5以前の場合、毎回違う配列をnewして渡す
// .NET Framework 4.6以降の場合、毎回同じインスタンスを使いまわす
if (prev == source)
{
return true;
}
prev = source;
return false;
}
}