🐥

C# 12で進化したコレクション操作の基本と応用

2025/02/03に公開
2

C# 12ではこのコレクションの扱い方がさらに進化し、より直感的に書けるようになりました。
この記事では日常的によく使うコレクション操作について、基本から実践的なテクニックまでを解説します。新しい機能を使いこなしてより読みやすく効率的なコードが書けるようになりましょう。

コレクションの要素にアクセスする新しい方法

配列やリストの特定の要素にアクセスする方法も、より直感的になりました。中でも便利なのは後ろからの要素取得です。(C# 8より導入されています)

var numbers = [10, 20, 30, 40, 50];

// 後ろから数えて1番目の要素(最後の要素)
var last = numbers[^1];     // 50

// 後ろから数えて2番目の要素
var secondLast = numbers[^2]; // 40

// 特定の範囲の要素を取得する
var slice1 = numbers[1..3];   // [20, 30]
var slice2 = numbers[..2];    // [10, 20]
var slice3 = numbers[3..];    // [40, 50]

範囲指定の詳しい説明

範囲指定(1..3のような書き方)については図で説明するとよりわかりやすいでしょう。

var items = [10, 20, 30, 40, 50];
//          ↑   ↑   ↑   ↑   ↑
// インデックス 0   1   2   3   4

// items[1..3] は「インデックス1から始めて、3の手前まで」という意味
var example = items[1..3];  // [20, 30]

重要なポイントは次の2つです

  1. 開始位置のインデックスの要素は含まれます
  2. 終了位置のインデックスの要素は含まれません(その手前までが対象です)

パフォーマンスについて知っておくべきこと

コレクションを扱う際、特に大量のデータを処理する場合はパフォーマンスについて意識することが大切です。以下で主な注意点と対策を説明します。

範囲指定とメモリ使用

範囲指定(list[1..3]のような書き方)を使うと新しい配列やリストが作られます。少量のデータなら問題ありませんが、大量のデータの場合は注意が必要です。

var numbers = [1, 2, 3, 4, 5, /* .. たくさんのデータ .. */];

// 方法1:新しい配列を作成(メモリを多く使う)
var slice = numbers[1..1000];  

// 方法2:イテレータを使用(メモリ効率が良い)
foreach (var item in numbers.Skip(1).Take(999))
{
    // 処理
}

データ量による使い分けの目安

一般的な使い分けの目安は以下の通りです。

  • 数十~数百程度の小さいデータの場合

    • 範囲指定(list[1..3])を使う
    • コードが読みやすい
    • 少量のメモリ使用なら問題ない
  • 数千以上の大きいデータの場合

    • Skip/Takeを使う
    • メモリ使用量が少ない
    • 処理が効率的

より簡単になったコレクションの作り方

これまでのC#では配列やリストを作る際に少し冗長な書き方が必要でしたが、C# 12から簡単に書けるようになりました。

// 従来の書き方
var numbers1 = new List<int> { 1, 2, 3, 4, 5 };
var array1 = new int[] { 1, 2, 3, 4, 5 };

// C# 12(プレビュー版)での新しい書き方
var numbers2 = [1, 2, 3, 4, 5];     // とてもシンプル!
int[] array2 = [1, 2, 3, 4, 5];     // 配列も同じように書けます

一目見ただけで新しい書き方がどれだけシンプルになったかがわかりますね。角括弧([])を使うだけで、コレクションを作れるようになりました。

コレクションの結合テクニック

さらに便利なのが既存のコレクションを組み合わせて新しいコレクションを作れることです。JavaScriptのスプレッド演算子(...)に似た機能で、とても直感的に書けます。
C#では .. で表現します。

var fruits1 = ["りんご", "みかん"];
var fruits2 = ["バナナ", "ぶどう"];

// 二つのコレクションを結合
var allFruits = [..fruits1, ..fruits2];  
// 結果:["りんご", "みかん", "バナナ", "ぶどう"]

// 途中に新しい要素を追加することもできます
var specialFruits = ["メロン", ..fruits1, "マンゴー"];
// 結果:["メロン", "りんご", "みかん", "マンゴー"]

この書き方を使えば、複数のコレクションを自由に組み合わせることができます。新しい要素の追加も自由自在です。

コレクションのパターンマッチング

C#ではコレクションの内容に基づいて処理を分岐させることができます。特にデータの構造に応じて異なる処理を行いたい場合に便利です。

var numbers = [1, 2, 3];
var message = numbers switch
{
    [] => "空のリストです",
    [var only] => $"要素が1つだけです:{only}",
    [var first, var second] => $"要素が2つあります:{first}{second}",
    _ => "要素が3つ以上あります"
};

このパターンマッチングは以下のような場合に特に役立ちます。

  • データの検証
  • 条件分岐が必要な処理
  • エラーハンドリング

実践的な使用例

実際のプログラミングでよくある場面での使用例を見てみましょう。

// ユーザーデータの処理
public class User
{
    public string Name { get; set; }
    public List<string> Hobbies { get; set; }
}

// C# 12の新しい書き方を使用
var user = new User
{
    Name = "田中太郎",
    Hobbies = ["読書", "プログラミング", "写真"]  // とてもシンプル!
};

// 趣味のリストを拡張
var additionalHobbies = ["旅行", "料理"];
user.Hobbies = [..user.Hobbies, ..additionalHobbies];

// 最新の3つの趣味だけを取得
var recentHobbies = user.Hobbies[^3..];

まとめ:シーン別・最適な使い方ガイド

C# 12のコレクション操作について学んできました。さいごに、「どんな時にどの機能を使うべきか」をシーン別にまとめてみました。実践で使える具体的なパターンとして覚えておくと良いでしょう。

シーン1:シンプルなコレクションの作成

🎯 こんな時には角括弧構文を使おう(プレビュー版機能)

// 配列やリストの初期化
var items = ["項目1", "項目2", "項目3"];  // シンプルで読みやすい

シーン2:既存のコレクションの結合

🎯 こんな時にはスプレッド演算子を使おう

var baseList = ["基本", "項目"];
var additions = ["追加", "項目"];
var combined = [..baseList, ..additions];  // 明確で直感的

シーン3:コレクションの末尾へのアクセス

🎯 こんな時には後ろからのインデックスを使おう

var items = ["古い", "やや古い", "新しい", "最新"];
var latest = items[^1];      // 最後の要素を取得
var recent = items[^2..];    // 最新の2つを取得

シーン4:データ量による使い分け

🎯 少量のデータ(数百件まで)の場合

var smallList = [/* 数百件のデータ */];
var slice = smallList[1..3];  // 範囲指定を使う

🎯 大量のデータ(数千件以上)の場合

var largeList = [/* 数千件以上のデータ */];
foreach (var item in largeList.Skip(1).Take(2))  // Skip/Takeを使う
{
    // 処理
}

シーン5:データの構造に応じた処理

🎯 こんな時にはパターンマッチングを使おう

var result = dataList switch
{
    [] => "データなし",
    [var single] => $"1件のみ:{single}",
    [var first, var second, ..] => "複数件あり"
};

実装時の黄金ルール

  1. 読みやすさ優先の場面

    • 小規模なデータ処理
    • コードレビューが頻繁に行われる部分
    • チーム開発の主要機能
      → 新しい角括弧構文やパターンマッチングを積極的に使用
  2. パフォーマンス優先の場面

    • 大量データの処理
    • ループ内での繰り返し処理
    • リアルタイム処理が必要な部分
      → Skip/Take や従来のループ処理を使用
  3. メンテナンス性優先の場面

    • 頻繁に仕様変更が入る部分
    • 複雑なビジネスロジック
    • 長期運用が予想される機能
      → パターンマッチングと明確な命名を組み合わせて使用

これらのパターンを意識しながら実装することで、より良いコードが書けるようになると思います。新しい機能はまずは小規模な範囲で試してみることをお勧めします。その上で、チームやプロジェクトの特性に合わせて、徐々に適用範囲を広げていくのがベストプラクティスです。


IT業界に、ITエンジニアに貢献する企業
ONE WEDGEはServerlessシステム開発を中核技術としてWeb系システム開発、AWS/GCPを利用した業務システム・サービス開発、PWAを用いたモバイル開発、Alexaスキル開発など、元気と技術力を武器にお客様に真摯に向き合う価値創造企業です。
https://onewedge.co.jp/

Discussion

junerjuner
ktnd111ktnd111

junerさん

ご指摘ありがとうございます。
本項目修正いたしました。jsのように・・・と書きながらがらそのままjsの記述になっておりましたが、正しくは .. です!リファレンスのリンクもいただきありがとうございます。