C# List<T> の使い方 - 要素の追加・検索・Sort・LINQ

2018/03/27 加筆修正。

はじめに

C#には配列と似た機能で、Listと呼ばれる概念があります。
Listは配列とは違い、動的に要素の追加や削除ができます。
この記事では、Listの基本的な使い方から、Listを用いた検索やソート、LINQまでを紹介します。

使い方

まず、usingステートメントに以下を宣言します。

using System.Collections.Generic;

このGeneric(ジェネリック)ですが、List型は、「型パラメータ」を利用します。
List

<T>

がGenericを指しています。
なので、usingステートメントで型パラメータを扱えるようにしておかないといけません。

Listの宣言

Listの生成

初期化する際は、以下の例の様に宣言します。

// int型のListを生成
List<int> listValue = new List<int>();

上記はint型のListですが、\<T>の部分はGenericなので他の型も指定出来ます。
例えば、以下はDictionaryを指定した際のListです。

// Dictionary<string, object>型のListを生成
List<Dictionary<string, object>> result = new List<Dictionary<string, object>>();

Listの初期化

Listは配列と同様、初期化のタイミングで値を入れる事が出来ます。

List<Dictionary<string, object>> result = new List<Dictionary<string, object>> () {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}

要素の追加

末尾に追加

末尾に追加する場合は、「add」を用います。

List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
// 末尾に追加
result.add("test-key3", "test-value3");
途中に追加

途中に追加する場合は、「Insert」を用います。

List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
// 2番目の要素に追加
result.Insert(2, "test-key3", "test-value3");

要素の削除

全て削除
List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
// 全て削除
result.Clear()
指定したインデックスで削除

配列と同様に、指定したインデックスでの削除も出来ます

List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
// 1番目の要素を削除
result.RemoveAt(1);

要素の取り出し方

配列と同様な取り出し方
List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
// 1番目の要素を取り出す
Dictionary<string, object> element = result[1];
foreachと組み合わせる

ListはIEnumerableインタフェースを採用しているので、foreachで取り出すことが出来ます。

List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}

foreach (Dictionary<string, object>element in result) {
   // elementに上から順にresultの要素が入る
}

Linqと組み合わせる

LinqはCollectionを柔軟に扱える機能だけあって、Listとの相性が良いです。
以下はLinqの使い方と活用事例です。

使い方

usingステートメントにLinqを追加します。

using Systen.Linq;
using System.Collections.Generic;

Listの中を検索して取り出し

// dictionaryのkeyが「test-key2」の物を探す
List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
Dictionary<string, object> element = result.Where(key => key == "test-key2");

Listの中を検索して削除

// dictionaryのkeyが「test-key2」の物を削除
List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
result.RemoveAll(key => key == "test-key2");

存在確認

// dictionaryのkeyが「test-key2」の物があるか確認
List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}

if (result.Ecists(key => key == "test-key2")) {
    // 存在
}

最小値・最大値を取得

最小値はMin, 最大値はMaxを使います。

List<int> numbers = new List<int> {11, 13, 32, 100, 203};

// 最小値
int min = numbers.Min();
// 最大値
int max = numbers.Max();

条件に一致した最小値を取得

MinもMaxもLINQで提供されているので、他のLINQと繋いで条件に一致した最小値も以下のように一気に記載できます。

List<int> numbers = new List<int> {11, 13, 32, 100, 203};

// 最小値
int min = numbers.Where(x => x > 50).Min();

上記の場合は、50以上の数の中での最小値を取得するので、100が取得できます。

平均値を取得

平均値を求めるにはAverageを利用します。

List<int> numbers = new List<int> {11, 13, 32, 100, 203};
int average = numbers.Average();

// 以下はコレクションの例. プロパティにnumberを持ったオブジェクトリストのnumberで平均値を取得
int average = sameObject.Average(x => x.number);

合計値を取得

合計値を求めるにはSumを利用します

List<int> numbers = new List<int> {11, 13, 32, 100, 203};
int average = numbers.Sum();

// 以下はコレクションの例. プロパティにnumberを持ったオブジェクトリストのnumberの合計値を取得
int average = sameObject.Sum(x => x.number);

カウント

条件に一致した要素をカウントするにはCountを使います。

List<int> numbers = new List<int> {11, 13, 32, 100, 203};
// 10以上の数をカウント
int count = numbers.Count(x => x > 10);

条件に一致する要素を調べる

条件に一致する要素があるかどうかはAnyを使います。
Anyは条件に一致した要素が見つかった時点で要素の走査をやめるので、高速に動作します。

List<int> numbers = new List<int> {11, 13, 32, 100, 203};
if (numbers.Any(x => x > 100)) {
   // 100以上が存在する
} else {
   // 100以上が存在しない
}

条件に一致する要素を全て取得する

条件に一致する要素を全て取得したい場合は、TakeWhileを使用します。

List<int> numbers = new List<int> {11, 13, 32, 100, 203};
// 50以上の数を全て取得する
List<int> numberResults = numbers.TakeWhile(x => x > 20);

上記の場合、32,100,203が取得できます。

条件に一致する要素を複数個取得する

一方、全てではなく、指定した個数を抜き出したい場合は、LINQのWhereとTakeを使って実現します。

List<int> numbers = new List<int> {11, 13, 32, 100, 203};
// 50以上の数を2個取得する
List<int> numberResults = numbers.Where(x => x > 50).Take(2);

上記の場合、100と203が取得されます。

List<T> を同じ値で埋める

同じ値で埋めるには、Repeatを使います。

// 10の要素100個分のList
List<int> numbers = Enumerable.Repeat(10, 100).ToList();
// "hoge"の要素100個分のList
List<string> hogeList = Enumerable.Repeat("hoge", 100).ToList();

上記のように、Repeatを用いる事で、同じ値を複数個用意し、その後ToListでListに変換しています。

List<T> を連続した値で埋める

連続した数値で埋めるには、Rangeを使います

// 1〜100の数値をセット
List<int> numbers = Enumerable.Range(1,100).ToList();

重複を排除した要素を抜き出す。

重複を排除したい場合は、Distinctを使います。

List<int> numbers = new List<int> {11, 13, 32, 100, 203};
List<int> resultNumbers = numbers.Distinct().ToList();

並べ替え・sort

並べ替え・sortを行うにはOrderBy, OrderByDescendingを使用します。

List<int> numbers = new List<int> {11, 13, 203, 32, 100};
// 昇順
var resultNumbers = numbers.OrderBy();
// 降順
var resultNumbersDesc = numbers.OrderByDescending();

応用

応用といっても、これまでの延長線ですが、例えばゲームでHPが60未満のキャラクターに対して回復魔法を実行する機能を作ってみます

class Charactor {
    string _Name;
    int _HP;

    public GetName() {
        return _Name;
    }
    public GetHP() {
        return _HP;
    }
    public SetHP(int hp) {
        _HP = hp;
    }

    public Charactor(string name, int hp) {
        _Name = name;
        _HP     = hp;
    }
}

class Main {
    void execute() {
        List<Charactor> charactorList = new List<Charactor>() {
            new Charactor("主人公", 100);
            new Charactor("戦士", 130);
            new Charactor("僧侶", 50);
            new Charactor("魔法使い", 30);
        }
        this.Heal(charactorList);
    }

    /**
      HPが60以下のキャラを回復させる
     */
    void Heal(List<Charactor> charactorList) {
        var healTarget = persons.Where( chara => chara.HP <= 60);
        foreach (var chara in healTarget) {
            // 10回復
            chara.SetHP(chara.GetHP() + 10);
        }
    }
}

さいごに

Listはそれ単体でも拡張性の高いデータ構造で大いに役に立ちますが、Linqと組み合わさる事で、簡単に要素へのアクセス・検索ができ、コードの可読性・生産性が大いに向上しますね。
私自身、Linqなどは使い始めたばかりですが、色々使って見て便利なやり方などが見つかったら引き続き記事に記載していこうと思います。