【C#】一定時間経過すると削除されるリストの実装

何の役に立つかはわかりませんが、一定時間経過したら削除されるリストを実装しててみました。

確認環境

  • .NET 6
  • VisualStudio 2022

実装コード

規定では Add(...) した後に、5秒以内に TryGetItemAndRemove() でデータを取り出されなければバックグラウンドのタイマー処理でデータが消去されます。

自動で削除された要素は AutoRemoved イベントで通知されるので後処理が必要ならイベントを購読します。

// 一定時間経過するとデータが消去されるリスト
public class ExpirationTimeItemHolder<TKey, TValue> : IDisposable  
    where TKey : IEquatable<TKey> 
    where TValue : class
{
    readonly System.Timers.Timer _timer;

    readonly List<ItemBug> _list = new List<ItemBug>();

    readonly object _lockObj = new object();
    
    private bool _isDisposed;

    // データの保持期間
    public TimeSpan HoldTime { get; set; } = TimeSpan.FromSeconds(5);

    // 保持期間が過ぎて自動で要素が削除された時に発生します
    public event Action<TValue> AutoRemoved;

    public ExpirationTimeItemHolder()
    {
        _timer = new System.Timers.Timer(100);
        _timer.Elapsed += OnTimerElapsed;
    }

    ~ExpirationTimeItemHolder() => Dispose(false);

    // 一定時間経過したら削除するタイマーハンドラー
    private void OnTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        var removeList = new List<ItemBug>();
        DateTime now = DateTime.Now;
        lock (_lockObj)
        {
            for (int i = 0; i < _list.Count; i++)
            {
                var item = _list[i];
                TimeSpan diff = now - item.Time;
                if (diff > HoldTime)
                {
                    removeList.Add(item);
                    Trace.WriteLine($"[diff] {diff.TotalMilliseconds}ms");
                }
            }

            for (int i = 0; i < removeList.Count; i++)
            {
                var removeItem = removeList[i];
                _list.Remove(removeItem);
                AutoRemoved?.Invoke(removeItem.Value);
                Trace.WriteLine($"[timer remove] {removeItem.Key}");
            }

            if (_list.Count == 0)
            {
                _timer.Stop();
                Trace.WriteLine("timer stop");
            }
        }
    }

    // データを追加する
    public void Add(TKey key, TValue value)
    {
        lock (_lockObj)
        {
            _list.Add(new ItemBug(key, DateTime.Now, value));
            Trace.WriteLine($"[add] {key}");
            if (!_timer.Enabled)
            {
                _timer.Start();
            }
        }
    }

    // データを取得してリストから削除する
    public bool TryGetItemAndRemove(TKey key, out TValue resultItem)
    {
        lock (_lockObj)
        {
            resultItem = default;
            ItemBug tempBug = null;

            for (int i = 0; i < _list.Count; i++)
            {
                var item = _list[i];
                if (key.Equals(item.Key))
                {
                    tempBug = item;
                    break;
                }
            }

            if (tempBug != null)
            {
                _list.Remove(tempBug);
                Trace.WriteLine($"[pop] {tempBug.Key}");
            }
            
            if (tempBug != null) resultItem = tempBug.Value;
            return resultItem != null;
        }
    }

    // InnerTypes

    private class ItemBug
    {
        public readonly TKey Key;
        public readonly DateTime Time;
        public readonly TValue Value;
        public ItemBug(TKey key, DateTime time, TValue value)
        {
            Key = key;
            Time = time;
            Value = value;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_isDisposed)
        {
            if (disposing)
            {
                lock (_lockObj)
                {
                    using (_timer) { }
                    AutoRemoved = null;
                }
            }
            _isDisposed = true;
        }
    }
}