Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections.Generic;
- using System.Xml.Linq;
- namespace DiscordTranslator.Bot.Logic
- {
- /// <summary>
- /// A Least Recently Used cache with optional forced expiry/eviction.
- /// </summary>
- public class LruCache<K, V> where K : notnull where V : notnull
- {
- private readonly int _capacity;
- private readonly Dictionary<K, LinkedListNode<LRUCacheItem>> _map = [];
- private readonly LinkedList<LRUCacheItem> _lru = new();
- private readonly Lock _sync = new();
- public int Count
- {
- get { lock (_sync) { return _lru.Count; } }
- }
- public LruCache(int capacity)
- {
- if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be positive.");
- _capacity = capacity;
- }
- /// <summary>
- /// Try to get a value by key and cast it to T (where T : V).
- /// Returns true and sets 'val' when found and castable; otherwise false and val = default.
- /// </summary>
- public bool TryGetValue<T>(K key, out T? val) where T : V
- {
- lock (_sync)
- {
- if (_map.TryGetValue(key, out var node))
- {
- // Move to back (most recently used)
- _lru.Remove(node);
- _lru.AddLast(node);
- if (node.Value.Value is T t)
- {
- val = t;
- return true;
- }
- }
- val = default;
- return false;
- }
- }
- public void Set(K key, V value)
- {
- lock (_sync)
- {
- if (_map.TryGetValue(key, out var existing))
- {
- existing.Value = existing.Value with { Value = value };
- // Refresh LRU position
- _lru.Remove(existing);
- _lru.AddLast(existing);
- }
- else
- {
- if (_map.Count >= _capacity)
- RemoveFirst(); // evict LRU
- var item = new LRUCacheItem(key, value);
- var node = new LinkedListNode<LRUCacheItem>(item);
- _lru.AddLast(node);
- _map[key] = node;
- }
- }
- }
- public void Remove(K key)
- {
- lock (_sync)
- {
- if (_map.TryGetValue(key, out var node))
- {
- _lru.Remove(node);
- _map.Remove(key);
- if (node.Value is IDisposable dispVal)
- {
- dispVal.Dispose();
- }
- }
- }
- }
- private void RemoveFirst()
- {
- // Evict least recently used (front)
- if (_lru.First is { } first)
- {
- _lru.RemoveFirst();
- // Remove from map using the evicted key
- _map.Remove(first.Value.Key);
- if (first.Value is IDisposable dispVal)
- {
- dispVal.Dispose();
- }
- }
- // If list was empty, nothing to evict (shouldn't happen with capacity checks)
- }
- internal sealed record LRUCacheItem(K Key, V Value)
- {
- public override string ToString() => $"{Key} => {Value}";
- }
- }
- }
Advertisement