C#で引数の宣言に配列をあんまり使わないほうがいいワケ
賛否あるかもしれませんが、
特別な理由がない場合配列やList<T>をメソッドの引数にとってはいけません。
じゃあ何をとるのかといえばIEnumerable<T>が大体の場合の正解です。
以下でその理由を書いていきます。
今回はこの2つのメソッドを見ていきましょう。
public void Write(string[] array){
foreach( var s in array )
{
Console.WriteLine(s);
}
}
public void Write(IEnumerable<string> enumerable){
foreach( var s in enumerable )
{
Console.WriteLine(s);
}
}
※型がstringなのは説明用です。
受けられる型の広さの問題
配列版とIEnumerable版を見比べてみます。
配列版とIEnumerable版どちらもstringの配列を引数にして呼び出すことはできます。
でも、配列版ではList<string>を引数にして呼び出すことができません。
つまり、IEnumerable版のほうが受けられる型が多いわけです。
更にLINQとの連携を考えましょう。
LINQは基本的にはIEnumerable<T>を戻り値にします。
つまり配列版はもちろんそのままでは呼び出すことができません。
どうしてもやりたいならばToArray()で配列化する必要があります。
string[] array = new []{"foo","bar","baz"};
Write(array);//これは配列版IEnumerable版どちらでもOK
//こうしただけで配列版はもう使えない
Write(array.Take(2));
//Write(array.Take(2).ToArray());こうするしかない
ここまでで、すでに配列特有の機能例えばランダムアクセスが必要であるとか、そういう理由がなければIEnumerableで引数を宣言したほうが良いことがわかります。
より一般化すると、同じ機能でもできるだけ使える範囲を広げておいたほうが便利なわけです。
副作用があると思われるかもしれない
配列というのはもちろんランダムアクセスも可能ですし、もちろん値を入れ替えることができます。
IEnumerableは基本的に(つまりキャストしたりしなければ)値を入れ替えることができません。
ということは、配列版では(もしたとえそうでなくても)使う側としてはもしかしたら配列の中身を書き換えられてしまうかもしれないと思うわけですね。
string[] array = new []{"foo","bar","baz"};
Write(array); //arrayの中身が書き換えられてしまうかもしれない
ということは書き換えられたくないような配列を安全に使うには毎回ToArrayをしてコピーする必要が出てきますね。
string[] array = new []{"foo","bar","baz"};
Write(array.ToArray()); //こうして書き換えられるのを防止する
より一般化すると、より抽象度の高い型で宣言しておいたほうが、メソッド内でできることが少なくなるので使う側としては安心になるわけです。
結論
基本的には、引数はできるだけ抽象度の高い型を指定しておく方がよいと思います。
ランダムアクセスやらなんやら他の機能が必要になったとかそうなって初めて引数に配列を入れることを検討しましょう。
コメントでそれぞれの型について色々書いてくださってます。
因みに、Listは次のinterfaceを実装しています。この中から必要最低限のinterfaceで宣言できるといいと思います。
(個人的にはやっぱりIEnumerable推しです。)
投稿してから3年経って、IReadOnlyCollectionをたまによく使うようになりました。
でもforeachだけならやっぱりIEnumerableが基本だと思います。
System.Collections.Generic.ICollection<T>, System.Collections.Generic.IEnumerable<T>,
System.Collections.Generic.IList<T>, System.Collections.Generic.IReadOnlyCollection<T>, System.Collections.Generic.IReadOnlyList<T>,
System.Collections.IList