Pangamma

LruCacheWithTTL - dotnet10 - c#

Jan 25th, 2026
3,260
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 5.14 KB | Source Code | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4.  
  5. namespace DiscordTranslator.Bot.Logic
  6. {
  7.     /// <summary>
  8.     /// A Least Recently Used cache implementation with forced expiry logic added on.
  9.     /// https://stackoverflow.com/a/3719378/1582837
  10.     /// https://searchstorage.techtarget.com/definition/cache-algorithm
  11.     /// Any extra questions just ask Taylor Love.
  12.     /// </summary>
  13.     /// <typeparam name="K"></typeparam>
  14.     /// <typeparam name="V"></typeparam>
  15.     public class LruCacheWithTTL<K, V> where K : notnull where V : notnull
  16.     {
  17.         private readonly int _capacity;
  18.         private readonly TimeSpan? _maxTTL;
  19.         private readonly Dictionary<K, LinkedListNode<LRUCacheItem>> _map = [];
  20.         private readonly LinkedList<LRUCacheItem> _lru = new();
  21.         private readonly Lock _sync = new();
  22.  
  23.         public int Count
  24.         {
  25.             get { lock (_sync) { return _lru.Count; } }
  26.         }
  27.  
  28.         public LruCacheWithTTL(int capacity)
  29.         {
  30.             this._capacity = capacity;
  31.             _maxTTL = null;
  32.         }
  33.  
  34.         public LruCacheWithTTL(int capacity, TimeSpan maxTTL)
  35.         {
  36.             this._capacity = capacity;
  37.             this._maxTTL = maxTTL;
  38.         }
  39.  
  40.         /// <summary>
  41.         /// Retrieves the value by key and then attempts to cast to type T. Returns
  42.         /// default(T) if value not in cache or value is not of needed type.
  43.         /// </summary>
  44.         /// <typeparam name="T"></typeparam>
  45.         /// <param name="key"></param>
  46.         /// <returns></returns>
  47.         public bool TryGetValue<T>(K key, out T? val) where T : V
  48.         {
  49.             val = default(T);
  50.  
  51.             lock (_sync)
  52.             {
  53.                 if (_map.TryGetValue(key, out var node))
  54.                 {
  55.                     if (node.Value.ForceExpireAt != null && node.Value.ForceExpireAt.Value < DateTime.UtcNow)
  56.                     {
  57.                         _lru.Remove(node);
  58.                         _map.Remove(key);
  59.                         return false;
  60.                     }
  61.  
  62.                     _lru.Remove(node);
  63.                     _lru.AddLast(node);
  64.                     if (node.Value.Value is T t)
  65.                     {
  66.                         val = t;
  67.                         return true;
  68.                     }
  69.                 }
  70.  
  71.                 val = default;
  72.                 return false;
  73.             }
  74.         }
  75.  
  76.         public bool Clear()
  77.         {
  78.             lock (_sync)
  79.             {
  80.                 _map.Clear();
  81.                 _lru.Clear();
  82.             }
  83.  
  84.             return true;
  85.         }
  86.  
  87.         public void Set(K key, V value, TimeSpan? maxTTL = null)
  88.         {
  89.             TimeSpan? ttl = maxTTL ?? this._maxTTL;
  90.             DateTime? expiry = ttl.HasValue ? DateTime.UtcNow.Add(ttl.Value) : null;
  91.  
  92.             lock (_sync)
  93.             {
  94.                 if (_map.TryGetValue(key, out var existing))
  95.                 {
  96.                     existing.Value = existing.Value with
  97.                     {
  98.                         Value = value,
  99.                         ForceExpireAt = expiry
  100.                     };
  101.  
  102.                     // Refresh LRU position
  103.                     _lru.Remove(existing);
  104.                     _lru.AddLast(existing);
  105.                 }
  106.                 else
  107.                 {
  108.                     if (_map.Count >= _capacity)
  109.                         RemoveFirst(); // evict LRU
  110.  
  111.                     LRUCacheItem item = new LRUCacheItem(key, value, expiry);
  112.                     var node = new LinkedListNode<LRUCacheItem>(item);
  113.                     _lru.AddLast(node);
  114.                     _map[key] = node;
  115.                 }
  116.             }
  117.         }
  118.  
  119.         public void Remove(K key)
  120.         {
  121.             lock (_sync)
  122.             {
  123.                 if (_map.TryGetValue(key, out var node))
  124.                 {
  125.                     _lru.Remove(node);   // Removal from LinkedList is O(1) if you have direct link to the Node.
  126.                     _map.Remove(key);
  127.                     if (node.Value is IDisposable dispVal)
  128.                     {
  129.                         dispVal.Dispose();
  130.                     }
  131.                 }
  132.             }
  133.         }
  134.  
  135.         private void RemoveFirst()
  136.         {
  137.             // Evict least recently used (front)
  138.             if (_lru.First is { } first)
  139.             {
  140.                 _lru.RemoveFirst();
  141.                 // Remove from map using the evicted key
  142.                 _map.Remove(first.Value.Key);
  143.  
  144.                 if (first.Value is IDisposable dispVal)
  145.                 {
  146.                     dispVal.Dispose();
  147.                 }
  148.             }
  149.         }
  150.  
  151.         internal sealed record LRUCacheItem(K Key, V Value, DateTime? ForceExpireAt)
  152.         {
  153.             public override string ToString()
  154.             {
  155.                 if (ForceExpireAt == null)
  156.                 {
  157.                     return $"{Key} => {Value} (No expiry)";
  158.                 }
  159.                 var timeRemaining = DateTime.UtcNow - ForceExpireAt.Value;
  160.                 return $"{Key} => {Value} (Expires: "+timeRemaining.ToString()+")";
  161.             }
  162.         }
  163.     }
  164. }
  165.  
Tags: lrucache
Advertisement