1. はじめに
コレクションの操作って、単にループしたりフィルタしたりソートするだけじゃないんだよね。よくあるのは、コレクションを自分の目的に合わせて変えたり作り直したりすること。例えば、文字列のコレクションを数字のコレクションにしたり、リストから重複を消したり、データ構造自体をガラッと変えたり。
こういうのは、標準のコレクションメソッドやシンプルなループ、基本的なアルゴリズムで全部できる。LINQよりちょっと長くなるけど、コレクションの仕組みを理解するにはめっちゃ役立つし、やってることが全部見えるから安心。
コレクションを変える主なやり方
コレクションの変更方法はいろいろあるけど、だいたい次のどれかになるよ:
- 中身の変更:要素を追加・削除・置き換えする。
- 要素の変換:元のコレクションから新しいコレクションを作る。型や構造、形を変える(例えばList<int>からList<string>にするとか)。
- 見た目の変更:コレクションを並べ替えたり、逆順にしたり、グループ化したり。
今回は1つ目と2つ目にフォーカスするよ。ソートは別のレクチャーで詳しくやってるからね。
変更可能コレクションと不変コレクション:大事なポイント
.NETのコレクションには、自由に中身を変えられるやつ(例えばList<T>)と、そうじゃないやつ(IReadOnlyList<T>や読み取り専用の配列とか)があるのを覚えておこう。
- 変更 — そのコレクション自体をいじる(Add、Remove、Clearとか)。
- 変換 — 元のコレクションはそのままで、新しいコレクションを作る。だいたい新しいリストを作って、元のコレクションの要素を入れていく感じ。
2. コレクションの中身を変更する
ICollection<T>を実装してるコレクションは、中身を変える基本メソッドが使える。例として本のカタログで見てみよう。
要素の追加と削除
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Year { get; set; }
}
List<Book> books = new List<Book>
{
new Book { Title = "クリーンコード", Author = "ロバート・マーチン", Year = 2008 },
new Book { Title = "CLR via C#", Author = "ジェフリー・リクター", Year = 2012 }
};
// 新しい本を追加
books.Add(new Book { Title = "Head First C#", Author = "アンドリュー・ステルマン", Year = 2010 });
// 条件で本を削除(例えば著者で)
for (int i = books.Count - 1; i >= 0; i--)
{
if (books[i].Author == "ロバート・マーチン")
books.RemoveAt(i);
}
// 特定のオブジェクトを削除(リストにあれば)
Book someBook = books[0];
books.Remove(someBook);
// コレクションを全部クリア
books.Clear();
// 指定した位置に要素を挿入
books.Insert(0, new Book { Title = "Pro C# 9", Author = "アンドリュー・トロエルセン", Year = 2021 });
要素の置き換え・更新
例えば出版年を間違えた場合、どう直す?
// ループで該当の本を探して年を修正
for (int i = 0; i < books.Count; i++)
{
if (books[i].Title == "Head First C#")
{
books[i].Year = 2018; // 年を修正
break;
}
}
3. コレクションの変換
要素の型を変換する(例:Bookから文字列へ)
全ての本のタイトルのコレクションを作る:
List<string> bookTitles = new List<string>();
foreach (Book book in books)
{
bookTitles.Add(book.Title);
}
foreach (var title in bookTitles)
{
Console.WriteLine(title);
}
全ての本の出版年を取得:
List<int> bookYears = new List<int>();
foreach (Book book in books)
{
bookYears.Add(book.Year);
}
新しい型・構造に変換:
public class BriefBookInfo
{
public string Title;
public int Year;
}
List<BriefBookInfo> briefInfos = new List<BriefBookInfo>();
foreach (Book book in books)
{
briefInfos.Add(new BriefBookInfo { Title = book.Title, Year = book.Year });
}
foreach (var info in briefInfos)
{
Console.WriteLine($"{info.Title} ({info.Year})");
}
コレクションのコレクションを1つにまとめる(flat map)
本にタグリストがある場合、全タグを1つのリストにまとめる:
public class Book
{
public string Title { get; set; }
public List<string> Tags { get; set; }
}
List<Book> booksWithTags = new List<Book>
{
new Book { Title = "クリーンコード", Tags = new List<string> { "Clean Code", "Refactoring" } },
new Book { Title = "CLR via C#", Tags = new List<string> { "CLR", "Internals" } }
};
List<string> allTags = new List<string>();
foreach (var book in booksWithTags)
{
foreach (var tag in book.Tags)
{
allTags.Add(tag);
}
}
foreach (string tag in allTags)
{
Console.WriteLine(tag);
}
4. よく使うコレクション変換メソッド
ToArray
リストから配列を作る:
string[] bookTitlesArray = bookTitles.ToArray();
Distinct — 重複を消す
ユニークな著者リスト(LINQなし):
List<string> authors = new List<string>();
foreach (Book book in books)
{
if (!authors.Contains(book.Author))
authors.Add(book.Author);
}
Reverse — コレクションを逆順にする
順番を逆にしたい時:
books.Reverse(); // その場で変更!
元のリストを変えたくない場合:
var reversed = new List<Book>(books);
reversed.Reverse();
ソート(ソートのレクチャー参照)
books.Sort((a, b) => a.Year.CompareTo(b.Year)); // 年でソート
フィールドでグループ化(GroupByのエミュレート)
著者ごとに本をグループ化("著者 — 本リスト"の辞書を作る):
Dictionary<string, List<Book>> booksByAuthor = new Dictionary<string, List<Book>>();
foreach (Book book in books)
{
if (!booksByAuthor.ContainsKey(book.Author))
booksByAuthor[book.Author] = new List<Book>();
booksByAuthor[book.Author].Add(book);
}
foreach (var pair in booksByAuthor)
{
Console.WriteLine($"著者: {pair.Key}");
foreach (var b in pair.Value)
Console.WriteLine($" {b.Title}");
}
5. 便利なポイント
- ループでの変換操作はどこでも確実に動く。
- 重複を除きたい時は、一時的なコレクションとContains/Addを使おう。
- foreachで回してる最中にコレクションを変えないこと!たくさん削除したい時は、まず削除対象を別リストに集めてからやろう。
6. よくある課題例
特定の年の本リストを取得
List<Book> recentBooks = new List<Book>();
foreach (Book book in books)
{
if (book.Year > 2010)
recentBooks.Add(book);
}
foreach (var book in recentBooks)
{
Console.WriteLine($"{book.Title} ({book.Year})");
}
全ての出版年(ユニーク)を昇順で出力
List<int> years = new List<int>();
foreach (Book book in books)
{
if (!years.Contains(book.Year))
years.Add(book.Year);
}
years.Sort();
foreach (int year in years)
Console.WriteLine(year);
本をエクスポート用に準備(DTOリストを作る)
public class BookExport
{
public string Name;
public string Writer;
public int PublishedYear;
}
List<BookExport> booksForExport = new List<BookExport>();
foreach (Book book in books)
{
booksForExport.Add(new BookExport
{
Name = book.Title,
Writer = book.Author,
PublishedYear = book.Year
});
}
7. よくあるミス・落とし穴
Add、Remove、Clear、Insert、Sortみたいなメソッドはコレクションをその場で変えちゃう。
新しいコレクションが欲しい時は、新しいリストを作って必要な要素だけ追加しよう。
コレクションをループしながら要素を消す時は、後ろから回す(for (int i = Count-1; i >= 0; i--))のが安全。
ユニークな要素だけのリストが欲しい時は、重複に注意してContainsやHashSet<T>を使おう。
GO TO FULL VERSION