【Unity】Listから条件に一致する要素をランダムに1個抽出する拡張メソッド

## 概要
LINQのWhereを極力使いたくない（Object allocationを極力避けたい）人向けです。

## 拡張メソッド

```C#:IEnumarableExtensions.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Object = System.Object;
using Random = UnityEngine.Random;

public static class IEnumarableExtensions
{
    private static readonly List<int> RandomIndexList = new List<int>();
    private static readonly Object LockObject = new Object();
    
    /// <summary>
    /// ieが空だと例外になるので要素の存在チェックを行ってから実行しましょう
    /// </summary>
    public static T GetAtRandom<T>(this IEnumerable<T> ie, Func<T,bool> predicate)
    {
        if (!ie.Any())
        {
            throw new Exception("要素が空です！");
        }
        // 非同期実行時に同時アクセスしないようlockします
        lock (LockObject)
        {
            RandomIndexList.Clear();

            // 条件に一致する要素のindexを取得します
            for (int i = 0; i < ie.Count(); i++)
            {
                if (predicate(ie.ElementAt(i)))
                {
                    RandomIndexList.Add(i);
                }
            }

            if (RandomIndexList.Count < 1)
            {
                throw new Exception("一致する要素がありません！");
            }
            
            // 抽出したindexから抽選して返します
            int randomIndex = RandomIndexList[Random.Range(0, RandomIndexList.Count)];
            return ie.ElementAt(randomIndex);
        }
    }
}
```

## ビフォアー
```C#:
void RandomBefore()
{
    List<int> intList = new List<int> {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
    int[] randomPool = intList.Where(x => x % 3 == 0).ToArray();
    
    for (int i = 0; i < 10; i++)
    {
        Debug.Log(randomPool[(Random.Range(0, randomPool.Length)]);
    }
}
```

## アフター
```C#:
void RandomAfter()
{
    List<int> intList = new List<int> {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
    // ** Whereが要らなくなる！！ **
    for (int i = 0; i < 10; i++)
    {
        Debug.Log(intList.GetAtRandom(x => x % 3 == 0));
    }
}
```

## 備考
タイトルには便宜的にListと書いてますが、IEnumerableを継承してれば使えます。
乱数の再現、シード値の固定をやりたい方は各自調整ください。


## 追記
大抵の場合、IReadOnlyListで間に合い、こちらの方が早いと紹介頂いたので、こちらにも載せます。

```C#:IReadOnlyListExtensions
using System;
using System.Collections.Generic;
using Object = System.Object;
using Random = UnityEngine.Random;

public static class IReadOnlyListExtensions
{    
    private static readonly int[] TempIndices = new int[1024];
    private static readonly Object LockObject = new Object();
    
    /// <summary>
    /// 候補が空のときはdefault値を返します
    /// </summary>
    public static T GetAtRandom<T>(this IReadOnlyList<T> ir, Func<T,bool> predicate)
    {
        lock (LockObject)
        {
            int count = 0;
            // 条件に一致する要素のindexを取得します
            for (int i = 0; i < ir.Count; i++)
            {
                if (predicate(ir[i]))
                {
                    TempIndices[count] = i;
                    count++;
                }
            }

            if (count == 0)
            {
                return default;
            }

            // 抽出したindexから抽選して返します
            int randomIndex = TempIndices[Random.Range(0, TempIndices.Length)];
            return ir[randomIndex];
        }
    }
}
```