【第二回】C#におけるCollectionsの基本のキを理解する【List<T>型】

はい第二回です。
第一回目はこちらです。
今回はコレクションの中で最も利用頻度が高いと思われるList<T>型について、ちょっぴり詳しめに説明していきますよ(解説というほど大層なものではないです)。
ただし例のごとく、僕のプログラミング歴は約3ヶ月と短いので、間違っている可能性山のごとしです。
その点だけご留意ください。

List<T>型を利用する前に

コレクションにはいくつかの種類があることを前回説明しました。
ある程度の経験を積めば直感的に「この場合はこのコレクションがいいな」とわかるのでしょうが、僕のような初心者は「一体どのコレクションを使えばええねん」と思うことがしばしばあります。
間違ったコレクションを選択すると、適切なコレクションを利用すればほんの数分で解決できることにやたらと手間取る可能性もあります
そこで参考にするのがこちらのサイトです。
Selecting a Collection Class

間違った型のコレクションを使用するとどうなるか

引用します。

原文:

Be sure to choose your collection class carefully. Using the wrong type can restrict your use of the collection. In general, avoid using the types in the System.Collections namespace unless you are specifically targeting .NET Framework version 1.1.

翻訳:

コレクションのクラスは必ず慎重に選んでください。間違った型のコレクションを利用すると、コレクションをうまく活用することができなくなります。一般的なことを言えば、.NET Framework version 1.1を特別に対象としている場合を除いて、System.Collections名前空間にある型の使用は避けてください。

「System.Collections名前空間にある型」とは、要はジェネリックでない型(もしくはConcurrentでない型)のことです。
普通にみなさんが開発する場合で.NET Framework version 1.1を利用することはまずないでしょうから(仕事での使用とかは知りませんよ)、ジェネリック型を使っておけば間違いありません。

さてジェネリック型を使うことは決まりました。
次はどのコレクションのクラス(リスト型やキュー型など)を選択するかですね。
これは本当に大切です。
例えばスタックを使うべき場所でキューを使えば当然プログラムは煩雑になりますし、スタックなら一発で解決できることをリストで解決しようとすれば、無駄なコードが増えます
そういった手間を避けるためにも、適切なコレクションの型を選ぶのは大切なのですね。

注*キューやスタックについてはあとで説明しますので、ここでは「そういった種類のコレクションがあるのだな」とだけ理解していただければ問題ありません。

で、どういったときにどういった型を利用すればよいのかもSelecting a Collection Classには書いているのですが……前提となる知識がちょっと多いので、原文の解説は省略します。
一つ一つのコレクションの説明をしつつ、最後にSelecting a Collection Classを改めてみるという構成にしようかなと思います(予定は未定)。

List<T>型はどんな型か

前回の記事でもList<T>型は使いましたね。
List<T>型は「配列に便利な機能が追加された、動的にサイズを変更できる型」と覚えておけば当たらずといえども遠からずでしょう(詳しい定義は前回の記事を読んでくださいね)。
ちなみにですが、List<T>型はあらかじめ「余分なサイズを含む大きさ」のメモリを確保します。で、その大きさで足らなくなったら、またさらに「余分なサイズを含む大きさ」のメモリを確保していくんですね。
つまり若干の無駄があるので(気にするほどなのかどうかはわかりませんw)、配列のサイズが固定で変わることがないなら、普通の配列を使ってもよいかもしれませんね。
僕もサイズが固定なら普通の配列を使うことが多いです。
ちなみにですが、余分なサイズ(正確に言うと、現時点で確保しているメモリに対して格納できる要素の数)がいくつなのかを調べるにはCapacityプロパティを利用します
僕は利用したことがありませんが、Capacityプロパティを設定することによって、上限値を決めることもできるようです(つまり普通の配列と同じように使うことができる)。

List<T>型の便利な機能

ここからは具体的にList<T>型の便利な機能を見ていきます。
参考にするのはList<T> Classです。

要素を末尾に追加する

要素(オブジェクト)を追加するには、Add(​T)やAdd​Range(​IEnumerable<​T>)を使用します
Add(T)は、リストの末尾に要素を追加します。これは前回のサンプルでも使用しましたね。
AddRange(​IEnumerable<​T>)は、リストとリストを連結するメソッドです。
Add(T)のList<T>型版と思っておけば問題ないでしょう(連結されたものはAdd(T)と同じく末尾に追加されます)。

要素を要素の途中に追加する

Insert(​Int32, ​T)やInsert​Range(​Int32, ​IEnumerable<​T>)ですね。
Addが末尾限定なのに対して、こちらは柔軟に位置を指定できることが特徴でしょう。
ただ注意してほしいのが、要素を要素の途中に追加した場合、他の要素もまとめてインデックスがずれる点です。
これが何を意味するかというと、プログラム内で固定されたインデックスに依存したコードを書いていた場合、そのプログラムがうまく動作しなくなります。
そして更に重要なのが、「要素を要素の途中に追加した場合、他の要素もまとめてインデックスがずれる」ということは、プログラムの内部で「わざわざ各インデックスを一つずつ順番にずらしている」ということと同義です。
もし10万の要素のあるリストを毎秒全てずらすようなプログラムを書いたとしたら、それはもう重いったらありゃしませんね。
こういう書き方に対応するコレクション(LinkedList<T>とかかなあ)もあるようなので、「このままだと処理が重くなりすぎるな」と思ったときは別のコレクションを検討するのも手かもしれませんね。

注*と長々と書きましたが、「要素を要素の途中に追加した場合、他の要素もまとめてインデックスがずれる」ということはどこかで読んだ記憶があるだけなので、間違ってたらご指摘ください……。

要素の合計を求める

List<T>型に格納されている要素の合計を求めるには、Countプロパティを使うのが一般的でしょう。
「今いくつの要素が入っているのか」によって挙動を変えるプログラムは、ゲームを作っているとよく出てきます(例えばメニュー系の画面とか)。
このプロパティは覚えておいたほうがよいですね。
また、配列の合計の要素数を取得するにはLengthプロパティを使用するのが一般的ですが、コレクションはCountプロパティである、という違いも頭に入れておくとよいと思います。

要素を消す

何らかの理由でリストの中身を初期化したいなら、Clear()を使うとよいでしょう。
全ての要素を消してくれます。
また、特定の要素を消したい場合はRemove(T)などを使うとよいです。
Remove(T)に類似するメソッドはいくつかありまして、インデックスを指定して消したり、範囲を指定して消したりなど、まあ様々です。
戻り値も種類によって変わるので、必要に応じて使用するメソッドを変えるのが吉ですね。

要素を検索する

要素の検索は結構複雑です。
と言うより使えるメソッドの種類が多いです。
一番単純なのはContains(​T)でしょう。これは、リストの中に要素が見つかればtrueを返し、そうでないならfalseを返すだけです。
細かい指定をしたいなら、Index​Of(​T, ​Int32)やExists(​Predicate<​T>)、Find​Index(​Predicate<​T>)なんかがあります。
検索の方法がいくつもあって面倒だと思うかもしれませんが、手段がいくつもあるということは、それだけ柔軟にListの中身を調べられるということです。
「こんな風に検索できないかな」と思ったら、リファレンスを覗いてみるのがよいですね。

List<T>型の紹介は終わり

List<T>型の簡単な紹介はとりあえず終わりました。
僕が具体的にゲームのどんなところでList<T>型を使っているかと言えば……まあ配列の代わりになる場所ならどこでも使えるんですが、最近ならそうですね。
ノベルゲームなんかでよくある会話ログ機能を実装する際に、おこなわれた会話を次々にリストに入れて、それを会話ログとして表示するプログラムを作りました。
当然他にも様々な用途で使えるので、List<T>型は覚えておくとよいですね。絶対に役立ちます。

次回は

次回はスタックとキューの基本的な部分を紹介します。
コレクションとしてのスタックとキューの紹介をする前に、「そもそもスタックとキューってなんやねん」というところから説明しようと思っています
逆に言えば、スタックとキューそのものについてはすでに理解している人には無用の長物の記事となりますが、スタックとキューを具体的にどこで使うと便利なのか、僕が実際に使った箇所を紹介しようとも思っています。
お時間があれば一読してみてください。

そんな感じで、また次回もよろしくお願いします。

フォローする

『【第二回】C#におけるCollectionsの基本のキを理解する【List<T>型】』へのコメント

  1. 名前:アチチノスエビ 投稿日:2017/07/11(火) 14:12:58 ID:13b50c45a

    内容がすごすぎますね・・・

    ただ翻訳なら任せて下さい

    Be sure to choose your collection class carefully. Using the wrong type can restrict your use of the collection. In general, avoid using the types in the System.Collections namespace unless you are specifically targeting .NET Framework version 1.1.

    もちろん、これから作るコレクションを設定する元のクラスは慎重に選ぶべきでしょう。作るプログラムの用途に相応しくないものを最初に選んでしまうと、それに後々縛られ続けることになるからです。分かりやすい例でいえば、(特別に旧アプリ互換が必要な仕事などの).NET 1.1対応設定が掛かったアプリとして作るような必要がなければ、System.Collections名前空間にある型の使用は避ける、となります。

    • 名前:ツミオ 投稿日:2017/07/11(火) 14:31:11 ID:6415f4815

      す、すばらしい!
      明らかにアチチノスエビさんの翻訳のほうがわかりやすいですね(特に前半)。
      英語も勉強しないとなあと思いました。