ここまでの章では、配列、ArrayList、List<T>と、コレクションそのものの使い方を順に見てきました。ここからは少し視点を変えて、それらのコレクションの背後にある「コレクションインタフェース」について解説していきます。
この章では、コレクションインタフェースとは何を指すのか、そしてなぜ存在しているのかという全体像を押さえます。具体的な使い方や内部の仕組みは次章以降で順に掘り下げていきますので、まずは「何のためにあるのか」をつかんでください。
検証用のボタンを追加する
これまでと同じように、フォームに検証用のボタンを1つ追加します。ボタンの表示名は「コレクションインタフェース」としておきます。
図1 「コレクションインタフェース」ボタンをフォームに追加する
追加したボタンをダブルクリックすると、クリックイベントのハンドラbutton18_Clickが自動生成されます。この章ではこのメソッドの中に、コレクションインタフェースの要点をコメントとして書き留めていきます。
図2 ダブルクリックで生成されたbutton18_Clickメソッド
リスト1 自動生成されたイベントハンドラ
private void button18_Click(object sender, EventArgs e)
{
}
コレクションインタフェースとは
コレクションインタフェースとは何かというと、大きく3つあります。IEnumerable<T>、ICollection<T>、IList<T>です。
図3 代表的な3つのコレクションインタフェース
リスト2 3つのコレクションインタフェース
private void button18_Click(object sender, EventArgs e)
{
//コレクションインタフェースとは
//IEnumerable<T>
//ICollection<T>
//IList<T>
}
C#でコードを書いていると、メソッドの引数や戻り値、プロパティの型などでよく見かける名前だと思います。これらのインタフェースは自分で定義するものではなく、.NETのクラスライブラリにあらかじめ用意されています。
たとえば次のように、List<T>のインスタンスはこの3つのどの型の変数にも代入できます。List<T>がこれらのインタフェースをすべて実装しているからです。
リスト3 List<T>は3つのインタフェースのどれとしても扱える(参考)
var list = new List<int> { 10, 22, 99 };
IEnumerable<int> a = list; // OK
ICollection<int> b = list; // OK
IList<int> c = list; // OK
では、こういったインタフェースはどういった役割をしているのでしょうか。
コレクションインタフェースが存在する2つの理由
コレクションインタフェースがなぜ存在しているかというと、大きく次の2つの意味があります。
リスト4 コレクションインタフェースが存在する2つの理由
private void button18_Click(object sender, EventArgs e)
{
//コレクションインタフェースとは
//IEnumerable<T>
//ICollection<T>
//IList<T>
//1. 異なるコレクションクラスに互換性を持たせる
//2. クライアントができる操作を制限させる
}
インタフェースですから、当然「何かしらの規格に合わせて実装する」ということになります。その規格があることで得られる効果が、この2つです。順に見ていきましょう。
1. 異なるコレクションクラスに互換性を持たせる
図4 1つ目の理由:異なるコレクションクラスに互換性を持たせる
1つ目は、インタフェースを使うことで、いろいろなコレクションを同一視できるという点です。
配列とList<T>はまったく別のクラスですが、どちらもIEnumerable<T>という同じ規格に合わせて実装されています。そのため、IEnumerable<T>を受け取るメソッドを1つ書いておけば、配列でもList<T>でも同じように渡すことができます。
リスト5 異なるコレクションを同じメソッドに渡す(参考)
private void button18_Click(object sender, EventArgs e)
{
int[] hai = { 10, 22, 99 };
var list = new List<int> { 10, 22, 99 };
// 配列もList<T>も、IEnumerable<int>として同一視できる
var result1 = Total(hai); // 131
var result2 = Total(list); // 131
}
private int Total(IEnumerable<int> values)
{
int total = 0;
foreach (var value in values)
{
total += value;
}
return total;
}
もしインタフェースがなければ、配列用のTotalメソッドとList<T>用のTotalメソッドを別々に用意しなければなりません。コレクションの種類が増えるたびにメソッドも増えていくことになります。インタフェースという共通の規格があるおかげで、異なるコレクションクラスの間に互換性が生まれるわけです。
2. クライアントができる操作を制限させる
図5 2つ目の理由:クライアントができる操作を制限させる
2つ目は、インタフェースを提供することで、クライアント側、つまりそのコレクションを受け取って使う側のコードにできることを狭めていけるという点です。クライアントができることを制御する、コントロールする、ということです。
List<T>をそのまま渡すと、受け取った側は要素の追加も削除も自由にできてしまいます。しかし同じList<T>でも、IEnumerable<T>として渡せば、受け取った側ができるのは基本的にforeachで順番に読み取ることだけになります。
リスト6 List<T>で受け取ると何でもできてしまう(参考)
private void button18_Click(object sender, EventArgs e)
{
var list = new List<int> { 10, 22, 99 };
ShowA(list);
ShowB(list);
}
// List<T>で受け取ると、何でもできてしまう
private void ShowA(List<int> values)
{
values.Add(100); // 追加できる
values.RemoveAt(0); // 削除もできる
values[0] = 999; // 書き換えもできる
}
一方、同じlistをIEnumerable<T>として受け取るShowBメソッドでは、次のようになります。
リスト7 IEnumerable<T>で受け取ると読み取ることしかできない(参考)
// IEnumerable<T>で受け取ると、読み取ることしかできない
private void ShowB(IEnumerable<int> values)
{
foreach (var value in values)
{
Console.WriteLine(value);
}
//values.Add(100); // コンパイルエラー:Addは定義されていない
//values[0] = 999; // コンパイルエラー:インデクサは定義されていない
}
渡しているインスタンスはどちらも同じList<T>です。違うのは「どのインタフェースとして見せるか」だけですが、それによってクライアントに許す操作の範囲が変わります。どのインタフェースを選ぶかは、そのまま「相手に何をさせたいか、何をさせたくないか」という設計上の意思表示になります。
まとめ
図6 この章で書き留めた内容の全体
コレクションインタフェースの代表はIEnumerable<T>、ICollection<T>、IList<T>の3つで、その存在理由は「異なるコレクションクラスに互換性を持たせる」ことと「クライアントができる操作を制限させる」ことの2つです。次章からは、この2つの観点を軸に、それぞれのインタフェースを具体的に解説していきます。
■非公開コース「C#14新機能」プレゼント:
非公開コース「C#14新機能」(80分)をご覧になりたい方は
こちらからURLとパスワードを発行していますので、ご覧になってみてください。
非公開コース「C#14新機能」を観る
A01_はじめに
A02_プロジェクトの作成
B01_配列とは
B02_配列の生成とアクセス
B03_生成と同時に値を設定する
B04_型推論による生成
B05_メソッドの引数などにする場合の注意点
B06_Length
B07_IndexOfでの検索
B08_FindIndexでの検索
B09_Find
B10_Exists
B11_FindAllとFindLast
B12_誤ったコピー
B13_Array.Copy
B14_範囲指定のコピー
B15_Resize
C01_ArrayList
C02_List
C03_List 動的な要素の変更
C04_Listのコンストラクタ
C05_Listのコンストラクタ_Capacity
C06_ListTからArrayクラスのメソッドが呼ばれている
D01_コレクションインタフェースとは
D02_異なるコレクションクラスに互換性を持たせる
D03_インタフェースの階層構造
D04_クライアントができる操作を制限させる
D05_クライアントができる操作を制限させる_後半
D06_Enumerableの拡張メソッドに関して
D07_ReadOnly系のコレクションインタフェース
D08_AsReadOnly
D09_ToListでコピーする
D10_ListTはprivateで使う
■非公開コース「C#14新機能」プレゼント:
非公開コース「C#14新機能」(80分)をご覧になりたい方は
こちらからURLとパスワードを発行していますので、ご覧になってみてください。
非公開コース「C#14新機能」を観る





