C#のループ処理におけるインデックス取得方法によるパフォーマンス比較してみた

## はじめに

日々C#で開発をしている中で、  
「**手動でインデックスをインクリメントするループ**」と  
「**LINQのSelectでインデックスを取得するループ**」  
のどちらがより効率的か、ふと疑問に思う場面がありました。

今回、簡単な検証を通して  
- **メモリ使用量**  
- **実行時間**  
にどれくらい差が出るのかを比較してみたので、その結果をまとめます。


## 実験内容

### 使用したコード

検証のために、以下のようなコードを作成しました。

#### オブジェクト作成とループ検証プログラム

```csharp
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;

public class MyData {
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }

    public MyData(int id, string name, int age)
    {
        Id = id;
        Name = name;
        Age = age;
    }
}

class Program
{
    public static string LOOPTYPE = "while"; // "for", "foreach", "foreachLinq", "while"
    public static int LOOPNUMBER = 1000000;

    static List<MyData> create(int numberOfObjects)
    {
        List<MyData> objects = new List<MyData>();
        for (int i = 0; i < numberOfObjects; i++)
        {
            var data = new MyData(i + 1, "pokepoke", 12);
            objects.Add(data);
        }
        Console.WriteLine($"作成したオブジェクト数: {objects.Count}");
        return objects;
    }
    
    static void Main()
    {
        var sw = new Stopwatch();
        List<MyData> sampleList = create(LOOPNUMBER);

        sw.Start();
        long initialMemory = GC.GetTotalMemory(false);
        Console.WriteLine($"初期メモリ: {initialMemory:N0} bytes");
        Console.WriteLine($"{LOOPTYPE}");
        
        for (int i = 0; i < 10; i++)
        {
            int[] id = new int[sampleList.Count];
            string[] name = new string[sampleList.Count];
            int index = 0;

            switch(LOOPTYPE){
                case "for":
                    for (int j = 0; j < sampleList.Count; j++){
                        id[j] = sampleList[j].Id;
                        name[j] = sampleList[j].Name;
                    }
                    break;
                case "foreach":
                    foreach(var value in sampleList){
                        id[index] = value.Id;
                        name[index] = value.Name;
                        index++;
                    }
                    break;
                case "foreachLinq":
                    foreach(var value in sampleList.Select((item,index) => new {item,index})){
                        id[value.index] = value.item.Id;
                        name[value.index] = value.item.Name;
                    }
                    break;
                case "while":
                    while (index < sampleList.Count){
                        id[index] = sampleList[index].Id;
                        name[index] = sampleList[index].Name;
                        index++;
                    }
                    break;
                default:
                    break;
            }

            long currentMemory = GC.GetTotalMemory(false);
            Console.WriteLine($"[{i}] メモリ: {currentMemory:N0} bytes 経過時間: {sw.ElapsedMilliseconds} ms");
            System.Threading.Thread.Sleep(500);
        }

        sw.Stop();
        Console.WriteLine($"総時間: {sw.ElapsedMilliseconds} ms");
    }
}
```
## コード要約

このコードは、

- `for` / `foreach` / `foreach+LINQ` / `while` の4つのループ方式で、
- 100万件など大量のオブジェクトに対して、
- メモリ使用量と処理時間を比較する

ための簡易パフォーマンス検証ツールです。



## 実験条件

### オブジェクト数
- 10,000件
- 100,000件
- 1,000,000件

### 計測対象
- メモリ使用量（`GC.GetTotalMemory(true)`使用）
- 実行時間（`Stopwatch`使用）

### 実行環境
- ※ここに開発環境を書く予定（例：Windows 11 / .NET 8 / Releaseビルド etc.）


## 実験結果

| オブジェクト数 | 方法 | メモリ増加量 | 実行時間 |
|:---|:---|:---|:---|
| 10,000 | 手動インクリメント | 少ない | 速い |
| 10,000 | LINQ Select | やや多い | やや遅い |
| 100,000 | 手動インクリメント | 少ない | 速い |
| 100,000 | LINQ Select | 多め | 遅い |
| 1,000,000 | 手動インクリメント | 少ない | やや遅い（でもまだ速い） |
| 1,000,000 | LINQ Select | さらに多い | さらに遅い |


## ざっくりまとめると

- オブジェクト数が多くなるほど差が開く
- 10,000件程度ならほぼ誤差レベル



## 考察（現場目線で）

今回の結果を現場で活かすなら、以下のような判断ができそうです。

| 条件 | 推奨方法 |
|:---|:---|
| 少量データ（数千〜数万件） | どちらでもOK。可読性重視でSelectもアリ |
| 大量データ（数十万件以上） | 手動インクリメントを使ったほうが無難 |

### 理由

- LINQを使うと「一時オブジェクト」が内部で生成されるため、オーバーヘッドが無視できない
- また、LINQの内部イテレータ（`SelectIterator`）によりメモリ圧迫・GCコストも増える
- 少量なら開発スピード優先、大量ならパフォーマンス優先がベター


## まとめ

- LINQのインデックス取得は少量データなら気にせず使ってOK
- 大量データ処理では、素直な`for`やインクリメント管理のほうが安全
- 可読性 vs パフォーマンス、どちらを重視すべきか現場ごとに判断する

今後も「なんとなくLINQを使う」ではなく、場面に応じた選択を意識していきたいと思いました。
記事の内容で間違いとかあれば指摘していただきたいです
今後の勉強にやくだてさせていただきます