えんじにあ雑記!

開発していて学んだことをまとめていきます!

C#のListに対する操作を簡潔に!

f:id:flat-M_M:20200408232858j:plain Listって色々なところで使いますよね。

要素の中から該当するものを探す、取り出す、削除するなどなど。

自前で書かずとも、それ、既にAPIにあるかもです👍

はじめに

リーダブルコード読み直しメモ Part.3の最後で少し書いたのですが、「公式リファレンスを読んでみるべし」と本に書いてあったので早速Listに関するリファレンスを読んで得た知見の中からいくつかピックアップしてまとめました。

前提

今回はStudentクラスを定義して、そのインスタンスを複数保持するListに対する操作で考えていきます。

Studentクラスの実装は下記の通りです。後にSortを使うためICompareを実装しています。

public enum Gender
{
    Male,
    Female,
    Other,
};

class Student : IComparer<Student>
{
    public int id;
    public int age;
    public Gender gender;

    public int Compare (Student x, Student y)
    {
        if (x.id == y.id) {
            return 0;
        } else if (x.id < y.id) {
            return -1;
        }
        return 1;
    }
}

また、指定数のStudentを生成してリストにしたものをシャッフルして返す生成用メソッドも下記の通り定義しています。

static List<Student> CreateRandomStudentsList(int count)
{
    var studentsList = new List<Student> ();
    var rand = new Random ();
    for (var i = 1; i <= count; i++) {
        var gen = Gender.Male;
        switch (i % 3) {
            case 1:
                gen = Gender.Female;
                break;
            case 2:
                gen = Gender.Other;
                break;
        }
        studentsList.Add (new Student { id = i, age = rand.Next (16, 19), gender = gen });
    }
    studentsList = studentsList.OrderBy (student => Guid.NewGuid ()).ToList ();
    return studentsList;
}

読み取り専用に変換「AsReadOnly」

Listに対して追加ならAdd、削除ならRemoveAtなどの操作がメソッドとして定義されています。

Listが変更可能な場合は便利ですが、変更不可能なListとして扱いたい場合もあります。

そういった場合にListクラスをラップしたオリジナルのReadOnlyListのようなクラスを作成することを考えるかもしれませんが、「AsReadOnly」を使えばReadOnlyCollection型として読み取り専用のリストとして扱うことができます。

参考に下記のコードを作成してみました。

static void SampleAsReadOnly()
{
    var students = CreateRandomStudentsList (30);
    var readonlyStudents = students.AsReadOnly ();
    // コンパイルエラー
    //readonlyStudents.Add (new Student { id = 100, age = 18, gender = Gender.Male });
}

AsReadOnlyで返されたreadonlyStudentsに対するAddは定義されていないのでコンパイルエラーとなります。

指定の要素が存在するか 「Exists」

次にList内に指定の条件に当てはまる要素が存在するかをチェックする「Exists」メソッドについてです。

例えば複数の学生情報を保持しているsstudentsリストから該当する出席番号の生徒が存在するかチェックするコードは下記の通りになります。

static void SampleExists()
{
    var students = CreateRandomStudentsList (30);
    if (students.Exists(student => student.id == 1)) {
        Console.WriteLine ("生徒ID[1]の生徒が見つかりました");
    }
    if (students.Exists(student => student.age > 18)) {
        Console.WriteLine ("高校生より上の学生が見つかりました");
    }
}

そしてこの出力結果は下記のようになります。

生徒ID[1]の生徒が見つかりました

このコードは

foreach (var student in students) {
    if (student.id == 1) {
        Console.WriteLine ("生徒ID[1]の生徒が見つかりました");
    }
}

のように書き直すこともできますが、コードの行数も一行で済みますし、公式のAPIを利用することでエンジニアの共通知識として一目でコードが理解しやすいメリットもありますね。

該当する要素を全て取り出す「FindAll」

次はリストの中から該当する項目を全て取ってきて、新しいリストとして結果を返してくれる「FindAll」メソッドについてです。

例えば学生のリストから男子学生だけを取り出して新しいリストを作成したい場合、FindAllを使わずに実装するならば下記のようになります。

static void SampleFindAll()
{
    var students = CreateRandomStudentsList (30);
    var menStudents = new List<Student> ();
    foreach (var student in students) {
        if (student.gender == Gender.Male) {
            menStudents.Add (student);
        }
    }
}

これを「FindAll」を使って書き直すと下記のように出来ます。

static void SampleFindAll()
{
    var students = CreateRandomStudentsList (30);
    var menStudents = students.FindAll (student => student.gender == Gender.Male);
}

コードも短くなり、より何をしているのか意図の伝わりやすいコードになったかと思います。

ここからここまで頂戴「GetRange」

範囲を指定して、一部分を抜き出したリストとして取得したい場合「GetRange」が使えます。

例えば出席番号順の昇順にソートした上で、最初の5人を取得したい場合は下記のように書けます。

static void SampleGetRange()
{
    var students = CreateRandomStudentsList (30);
    students.Sort (new Student());
    var firstFiveStudents = students.GetRange (0, 5);
    firstFiveStudents.ForEach (s => Console.WriteLine (s.id));
}

簡潔かつ綺麗なコードになっていますね。このコードの出力結果は下記の通りになります。

1
2
3
4
5

正しくソートした後、最初の5人を取得できています。

該当する要素を取り除く「RemoveAll」

最後に条件に合致した要素を除去してくれる「RemoveAll」についてです。

FindAllの逆かと思いきや、RemoveAllの返り値は削除した要素の個数になっている点は注意です。

例えば、年齢が18歳未満の学生、かつ女性を除いたリストを作成したい場合を考えてみます。

実装したコードは下記の通りになります。

static void SampleRemoveAll()
{
    var students = CreateRandomStudentsList (30);
    students.RemoveAll (student => student.age < 18 || student.gender == Gender.Female);
    students.ForEach (s => Console.WriteLine ($"ID: {s.id} Age: {s.age} Gender: {s.gender}"));
}

そしてこちらの出力結果は次の通りになります。

ID: 30 Age: 18 Gender: Male
ID: 27 Age: 18 Gender: Male
ID: 23 Age: 18 Gender: Other
ID: 21 Age: 18 Gender: Male
ID: 29 Age: 18 Gender: Other

条件に合致するもの以外は取り除かれていることがわかります!

まとめ

今回はC#のListAPIについて、知っておくと少し得しそうなAPIを紹介しました!

どれも自分でゴリ押し実装できるようなものですが、APIを知っていれば一行でさくっとかけてしまうことでもあります。

またコードの理解のしやすさも上がるでしょう。

今後も時間を見つけて公式ドキュメントを読んでいこうと思います。