コレクション【C#】

C#文法:コレクション:D02_異なるコレクションクラスに互換性を持たせる

ここからは「異なるコレクションクラスに互換性を持たせる」というテーマを扱います。前回お話ししたとおり、コレクションインタフェースとはそもそも何であるのか、という点を見ていきます。

コレクションインタフェースには IEnumerable<T>、ICollection<T>、IList<T> があり、その役割は大きく2つあります。1つ目が「異なるコレクションクラスに互換性を持たせる」こと、2つ目が「クライアントができる操作を制限させる」ことです。今回は1つ目を取り上げます。

図1 コレクションインタフェースの2つの役割(今回は1.を扱う)

図1 コレクションインタフェースの2つの役割(今回は1.を扱う)

もしコレクションインタフェースがなかったら

必要性を考えるうえで、まずは「もしこのインタフェースがなかったらどうなるか」を一度考えてみます。題材として、独自のListクラスを自作するところから始めましょう。

MyList という名前でクラスを作ります。インタフェースは特に実装せず、ジェネリックの型パラメーター T を指定できるようにします。

図2 インタフェースを実装しないMyList<T>クラスを作成する

図2 インタフェースを実装しないMyList<T>クラスを作成する

リスト1 MyList<T>クラスの骨格

public class MyList<T>
{
}

次に、要素を保持するための入れ物として、T型の配列 _hai をフィールドに用意します。最初は要素数0で new しておきます。

図3 T型の配列_haiを要素数0で生成する

図3 T型の配列_haiを要素数0で生成する

リスト2 要素を保持する配列フィールド

public class MyList<T>
{
    private T[] _hai = new T[0];
}

Addメソッドを実装する

続いて、要素を追加する Add メソッドを作ります。引数で item が渡されてきたら、配列 _hai にその値を入れる、という処理です。

配列は生成後に要素数を変えられないので、ここでは Array.Resize を使います。第1引数に ref _hai、第2引数に新しいサイズとして _hai.Length + 1 を指定します。これで配列のサイズが1つ増えます。

図4 Array.Resizeの第1引数にref _haiを指定する

図4 Array.Resizeの第1引数にref _haiを指定する

サイズを増やしたら、配列の Length - 1、つまり最後の要素に item を代入します。要するに「要素を最後に追加する」という動きです。

図5 最後の要素にitemを代入してAddメソッドが完成

図5 最後の要素にitemを代入してAddメソッドが完成

リスト3 MyList<T>クラス(Addメソッドまで)

public class MyList<T>
{
    private T[] _hai = new T[0];

    public void Add(T item)
    {
        // 配列のサイズを1つ増やす
        Array.Resize(ref _hai, _hai.Length + 1);
        // 最後の要素にitemを入れる
        _hai[_hai.Length - 1] = item;
    }
}

これで、インタフェースを何も実装していない、自作の MyList<T> ができあがりました。

MyList<int>を受け取るメソッドを作る

次に、この MyList<T> を引数に受け取るメソッドを考えます。たとえば SearchIndex という名前で、MyList<int> と value を渡すと、その値のインデックスを返してくれるメソッドがあったとします。中身の実装は今回の本題ではないので省略し、return 0; としておきます。

図6 MyList<int>を引数に取るSearchIndexメソッド

図6 MyList<int>を引数に取るSearchIndexメソッド

リスト4 SearchIndexメソッド(引数の型はMyList<int>)

private int SearchIndex(MyList<int> hai, int value)
{
    return 0;//実装なし
}

ここで注目してほしいのは第1引数の型です。引数が MyList<int> になっているので、当然このメソッドには MyList<int> しか渡せません。

MyList<int>を渡す場合

実際に呼び出してみます。MyList<int> を生成し、何かしら Add しておいて、SearchIndex に myList と、探したい値として 20 を渡します。

図7 MyList<int>をSearchIndexに渡す

図7 MyList<int>をSearchIndexに渡す

リスト5 MyList<int>を渡す(問題なくコンパイルできる)

//1.異なるコレクションクラスに互換性を持たせる
MyList<int> myList = new MyList<int>();
myList.Add(10);
myList.Add(20);
var myListIndex = SearchIndex(myList, 20);

このように書けばインデックスを取得できます。引数の型と渡している型が一致しているので、何も問題はありません。

List<int>を渡すとコンパイルエラーになる

では、クラスライブラリの List<T> を渡そうとするとどうなるでしょうか。List<int> を生成して、同じように SearchIndex に渡してみます。

図8 List<int>をSearchIndexに渡すと赤い波線が表示される

図8 List<int>をSearchIndexに渡すと赤い波線が表示される

リスト6 List<int>を渡す(コンパイルエラー)

List<int> list = new List<int>();
var listIndex = SearchIndex(list, 20);   // コンパイルエラー CS1503

当然、コンパイルエラーになります。エラー箇所にマウスカーソルを合わせると、次のメッセージが表示されます。

図9 CS1503:List<int>からMyList<int>へ変換することはできません

図9 CS1503:List<int>からMyList<int>へ変換することはできません

リスト7 表示されるエラーメッセージ

CS1503: 引数 1: は 'System.Collections.Generic.List<int>' から
'WinFormsApp1.MyList<int>' へ変換することはできません

「MyList に変換できません」と言われています。SearchIndex は MyList<int> で受けているので、MyList<int> しか渡してはいけない、ということです。

互換性がないという問題

このように作ってしまうと、互換性のないものになってしまいます。SearchIndex は MyList でしか使えないメソッドになっているわけです。

しかし、世の中にはいろいろなListがあります。メソッドを書く側からすれば「別にどんなListでもかまわないのに」と思う場面は多いはずです。MyList であろうと、クラスライブラリの List であろうと、同じものとして扱ってくれるなら、SearchIndex というメソッドは「どちらを渡してもよい」ものになります。

コレクションインタフェースを実装して同一視する

そこで登場するのがコレクションインタフェースです。コレクションクラスを自作すること自体はかまいません。ただし、そのときにコレクションインタフェースを一緒に実装しておくことで、クラスライブラリとの互換性が出ますし、自分たちの製品コードの中でも互換性が出ます。

今回の例であれば、IList<T> を MyList<T> にも実装させておきます。そうすれば、List<T> も渡せるし MyList<T> も渡せる、という同一視ができるようになります。

参考として、引数の型を IList<int> に変えた場合のイメージを示します。MyList<T> に IList<T> を実装する具体的な方法は、以降のレクチャーで扱います。

リスト8 【参考】引数の型をIList<int>にした場合のイメージ

// 引数をインタフェースで受ける
private int SearchIndex(IList<int> hai, int value)
{
    return 0;//実装なし
}

// List<T>はIList<T>を実装しているので渡せる
List<int> list = new List<int>();
var listIndex = SearchIndex(list, 20);

// MyList<T>にもIList<T>を実装しておけば、同じように渡せる
// public class MyList<T> : IList<T> { ... }
MyList<int> myList = new MyList<int>();
var myListIndex = SearchIndex(myList, 20);

レクチャーでは、ここまでの内容をコメントとしてコードに残しています。

図10 コレクションインタフェースを実装することでクラスライブラリとの互換性が出る

図10 コレクションインタフェースを実装することでクラスライブラリとの互換性が出る

リスト9 今回のまとめコメント

//コレクションクラスを自作してもよいが、
//コレクションインタフェースを実装することで、
//クラスライブラリとの互換性が出る。
//コレクションインタフェースは大きく3つある。
//IEnumerable<T>
//ICollection<T>
//IList<T>

図11 コレクションインタフェースは大きく3つある

図11 コレクションインタフェースは大きく3つある

まとめ

インタフェースを実装していない自作コレクションクラスは、そのクラスを引数に取るメソッドでしか使えず、List<T> のような他のコレクションクラスとの互換性がありません。コレクションインタフェースを実装しておくことで、異なるコレクションクラスを同じものとして扱えるようになります。

この同一視するためのインタフェースは、大きく分けて IEnumerable<T>、ICollection<T>、IList<T> の3種類があります。次回以降、それぞれをどのように使っていくのかを見ていきます。

C#文法:コレクション

■非公開コース「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新機能」を観る