今回はList<T>のコンストラクタを見ていきます。List<T>はnewするときに渡す引数によって、空の状態から始めることも、既存のコレクションの中身を引き継いだ状態から始めることもできます。それぞれの書き方と、コピーとして使う場合の挙動を順番に確認していきましょう。
検証用のボタンを追加する
これまでと同じように、フォームに検証用のボタンを1つ追加します。ボタンのテキストは「List<T>のコンストラクタ」とし、ダブルクリックしてクリックイベントのメソッドを生成します。
図1 「List<T>のコンストラクタ」ボタンを追加してダブルクリックする
リスト1 生成されたクリックイベント
private void button16_Click(object sender, EventArgs e)
{
//List<T>のコンストラクタ
}
List<T>のコンストラクタは3種類
List<T>のコンストラクタとは、new List<string>()と書いたときの丸括弧の部分のことです。new List<string>(まで入力すると、Visual Studioが引数の候補を表示してくれます。候補は全部で3つあり、1つ目は引数なしのコンストラクタです。
図2 1/3:引数なしのコンストラクタ List<string>()
下向きの矢印で候補を切り替えると、2つ目はint型のcapacityを受け取るコンストラクタであることがわかります。
図3 2/3:int capacity を受け取るコンストラクタ
3つ目はIEnumerable<T>型のcollectionを受け取るコンストラクタです。Tは型パラメーターなので、List<string>であればIEnumerable<string>を渡すことになります。
図4 3/3:IEnumerable<string> collection を受け取るコンストラクタ
整理すると、List<T>には次の3つの渡し方があります。
リスト2 List<T>の3つのコンストラクタ
public List(); //引数なし public List(int capacity); //初期容量を指定 public List(IEnumerable<T> collection); //コレクションを指定
本章ではこのうち、もっともよく使う「引数なし」と、コピーに使える「IEnumerable<T>を指定する」書き方を実際に動かして確認します。
引数なしのコンストラクタ
まずは引数なしからです。これは空のList<T>を作ってスタートする、もっとも基本的な書き方です。生成した直後は要素が1件もないので、必要に応じてAddで要素を追加していきます。
リスト3 引数なしで生成して要素を追加する
//List<T>のコンストラクタ
var list = new List<string>();
list.Add("X");
図5 引数なしで生成し、Addで要素を追加する
初期化子で初期値を入れる
コンストラクタに何も渡さなくても、初期化子を使えば初期値を入れることができます。コンストラクタの丸括弧は空のままにして、その後ろに中括弧を付け、カンマ区切りで要素を並べます。
リスト4 初期化子で初期値を設定する
var aList = new List<string>() { "AAA","B","CC"};
ブレークポイントを置いて実行し、aListにマウスカーソルを合わせてみます。Count = 3となっており、インデックス0から順に”AAA”、”B”、”CC”が入っていることが確認できます。
図6 データヒントで確認すると、初期値の3件が入っている
つまり、空っぽから始めればよい場合は引数なしのコンストラクタだけ、最初から値を入れておきたい場合は引数なしのコンストラクタに初期化子を付ける、という使い分けになります。
IEnumerable<T>を指定するコンストラクタ
次は、コンストラクタの第1引数にIEnumerable<T>を指定する書き方です。IEnumerable<T>ということは、何かしらのコレクションを意味します。配列やList<T>などがそれにあたります。これらを指定すると、そこから要素を読み取って新しいList<T>を作成してくれます。
リスト5 IEnumerable<T>を指定して生成する
//コンストラクタ:IEnumerable<T> //IEnumerable<T>から要素を読み取り、新しいList<T>を作成する var bList = new List<string>(aList);
図7 先ほどのaListを第1引数に渡してbListを生成する
List<T>はIEnumerable<T>を実装している
ここでaListを渡せるのは、List<T>がIEnumerable<T>を実装しているからです。Listにカーソルを置いてF12キーを押すと定義に移動でき、クラス宣言を見ると実装しているインターフェースの中にIEnumerable<T>があることがわかります。
図8 F12でList<T>の定義に移動すると、IEnumerable<T>が実装されている
リスト6 List<T>のクラス宣言(メタデータより)
public class List<T> : ICollection<T>, IEnumerable<T>, IEnumerable,
IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection, IList
このIEnumerable<T>が実装されていれば、コンストラクタに渡すことができます。配列やList<T>といったコレクション系のものは基本的に渡せる、と考えておけば大丈夫です。
連動しないコピーになる
この書き方をすると、配列をArray.Copyでコピーしたときと同じように、参照代入の問題が起きないコピーになります。要するに、コピー元とコピー先が連動しません。そのため、bListにAddしても元ネタのaListには影響しないはずです。
リスト7 コピー先に要素を追加する
var bList = new List<string>(aList);
//Array.Copyのようにコピーできる(参照代入ではないやり方)
bList.Add("D");//元ネタに影響がない
実行してウォッチウィンドウでaListとbListを並べて確認します。bList.Add("D")を実行すると、bListはCount = 4となり、末尾に”D”が追加されています。
図9 bListには”D”が追加されてCount = 4になる
一方でaListのほうは変わっていません。3件のままです。aListをbListにコピーしたうえでbListを変更しても、元ネタには影響が出ないことが確認できました。
図10 aListはCount = 3のままで、元ネタには影響していない
比較のために、コンストラクタを使わずに単純に代入した場合を示しておきます。こちらは同じList<T>を2つの変数が指しているだけなので、片方にAddするともう片方にも反映されてしまいます。
リスト8 参考:参照代入の場合は連動してしまう
var aList = new List<string>() { "AAA","B","CC"};
var xList = aList; //参照代入
xList.Add("D"); //aListも4件になる(連動する)
var bList = new List<string>(aList); //コンストラクタでコピー
bList.Add("E"); //aListは変わらない(連動しない)
配列を渡してもよい
コンストラクタに渡すものはList<T>でなくても構いません。IEnumerable<T>が実装されていればよいので、stringの配列でも同じように書けます。配列haiを用意し、それをcListのコンストラクタに渡してみます。
リスト9 配列からList<T>を生成する
var hai = new string[2] { "X", "YY" };
var cList = new List<string>(hai);
cList.Add("XXX");
図11 コンストラクタの第1引数に配列haiを渡す
この場合も連動しないはずです。実行して確認すると、cListには”XXX”が追加されてCount = 3になっています。
図12 cListには”XXX”が追加されてCount = 3になる
元ネタである配列haiのほうはstring[2]のままで、”X”と”YY”の2件から変化していません。
図13 元ネタの配列haiは2件のままで影響を受けていない
このように、既存のコレクションをコピーして新しいList<T>を作りたいときは、コンストラクタの第1引数にそのコレクションを入れればよい、ということです。
ToListでもコピーできる
もう1つ、ToListメソッドを使ってもコピーできます。配列haiに対してToList()を呼び出し、その結果をList<string>型のcList2で受け取ります。
リスト10 ToListでコピーする
//ToList()でもコピーできる
List<string> cList2 = hai.ToList();
cList2.Add("YYY");
図14 配列haiに対してToList()を呼び出す
こちらも実行して確認します。cList2には”YYY”が追加されてCount = 3になっています。
図15 cList2には”YYY”が追加されてCount = 3になる
そして元ネタの配列haiは、やはり変化していません。ToListもコンストラクタと同じく、連動しないコピーを作ってくれることがわかります。
図16 配列haiは2件のままで変化していない
まとめ
今回書いたコードの全体は次のとおりです。
リスト11 今回のコード全体
private void button16_Click(object sender, EventArgs e)
{
//List<T>のコンストラクタ
var list = new List<string>();
list.Add("X");
var aList = new List<string>() { "AAA","B","CC"};
//コンストラクタ:IEnumerable<T>
//IEnumerable<T>から要素を読み取り、新しいList<T>を作成する
var bList = new List<string>(aList);
//Array.Copyのようにコピーできる(参照代入ではないやり方)
bList.Add("D");//元ネタに影響がない
var hai = new string[2] { "X", "YY" };
var cList = new List<string>(hai);
cList.Add("XXX");
//ToList()でもコピーできる
List<string> cList2 = hai.ToList();
cList2.Add("YYY");
}
図17 完成したコード
空の状態から始めるなら引数なしのコンストラクタ、初期値を入れたいなら初期化子を使います。そして既存のコレクションをコピーしたいときは、ToListでコピーするか、もしくはコンストラクタの第1引数にIEnumerable<T>を渡してください。どちらの方法でも、元ネタと連動しない新しいList<T>が得られます。
■非公開コース「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新機能」を観る
















