IEnumerable<T> / IList<T>
| 対応: | C# 1.0(2002) |
|---|
コレクションを抽象化する基底インターフェース『IEnumerable<T>』と『IList<T>』です。メソッドの引数や戻り値の型として使うことで、柔軟な設計が実現できます。
構文
// IEnumerable<T>: foreach でループできる最小限のインターフェースです。 IEnumerable<T> variable = new List<T>(); // IList<T>: インデックスアクセスと追加・削除ができるインターフェースです。 IList<T> variable = new List<T>(); // IReadOnlyList<T>: 読み取り専用のリストインターフェースです。 IReadOnlyList<T> variable = new List<T>();
インターフェース一覧
| インターフェース | 概要 |
|---|---|
| IEnumerable<T> | foreach でループする機能のみを持つ最も基本的なインターフェースです。LINQ の拡張メソッドが使えます。 |
| ICollection<T> | 要素数(Count)と追加・削除・含有チェックの機能を持ちます。 |
| IList<T> | ICollection に加え、インデックスによるアクセス([i])と挿入・削除をサポートします。 |
| IReadOnlyList<T> | 読み取り専用のリストです。Count とインデックスアクセスは使えますが、変更はできません。 |
| IReadOnlyCollection<T> | Count と foreach のみ使える読み取り専用コレクションです。 |
サンプルコード
IEnumerable<T> を引数の型にすると、List・配列・HashSet などどんなコレクションも受け取れます。
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
void Print(IEnumerable<string> items)
{
foreach (string item in items)
{
Console.Write(item + " ");
}
Console.WriteLine();
}
List<string> list = new List<string> { "C#", "Java", "Python" };
string[] array = { "Go", "Rust", "Swift" };
HashSet<string> set = new HashSet<string> { "Ruby", "PHP" };
Print(list); // C# Java Python
Print(array); // Go Rust Swift
Print(set); // Ruby PHP
IEnumerable<int> sequence = new int[] { 1, 2, 3, 4, 5 };
Console.WriteLine(sequence.Where(x => x % 2 == 0).Sum()); // 6
dotnet run C# Java Python Go Rust Swift Ruby PHP 6
IList<T> と IReadOnlyList<T>
IList<T> はインデックスアクセスと追加・削除ができます。IReadOnlyList<T> は読み取りのみで、外部からの変更を防ぎたい場合に使います。
IListSample.cs
using System;
using System.Collections.Generic;
IList<int> numbers = new List<int> { 10, 20, 30 };
Console.WriteLine(numbers[1]); // 20
numbers.Add(40);
Console.WriteLine(numbers.Count); // 4
IReadOnlyList<string> readOnly = new List<string> { "A", "B", "C" };
Console.WriteLine(readOnly[0]); // A
Console.WriteLine(readOnly.Count); // 3
// readOnly.Add("D"); // コンパイルエラー
dotnet run 20 4 A 3
よくあるミス
よくあるミス: 具体的な List<T> をそのまま公開する
クラスの内部データを List<T> のまま公開すると、外部から要素を追加・削除されてしまいます。読み取り専用にするには IReadOnlyList<T> や AsReadOnly() を使います。
using System;
using System.Collections.Generic;
class Team
{
// NG: List をそのまま公開すると外部から変更される
public List<string> Members = new List<string> { "碇シンジ", "綾波レイ" };
}
Team team = new Team();
team.Members.Add("惣流アスカ"); // 外部から変更できてしまう
Console.WriteLine(team.Members.Count); // 3
修正後は次の通りです。
using System;
using System.Collections.Generic;
class Team
{
private List<string> _members = new List<string> { "碇シンジ", "綾波レイ" };
// OK: IReadOnlyList で読み取り専用として公開する
public IReadOnlyList<string> Members => _members;
public void AddMember(string name) => _members.Add(name);
}
Team team = new Team();
// team.Members.Add("惣流アスカ"); // コンパイルエラー(変更不可)
team.AddMember("惣流アスカ"); // メソッド経由で追加する
Console.WriteLine(team.Members.Count); // 3
dotnet run 3
概要
メソッドの引数型は、必要な機能に対して最も制約が緩いインターフェースを選ぶのがベストプラクティスです。コレクションを読むだけなら IEnumerable、要素数が必要なら ICollection、インデックスアクセスが必要なら IList を使います。
クラスの内部データを外部に公開する際には IReadOnlyList<T> が有効です。具体的な List<T> をそのまま公開すると外部からデータを書き換えられてしまいます。
重複なしのコレクションは『HashSet<T>』、Queue・Stack は『Queue<T> / Stack<T>』を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。