Pangamma icon

LruCache - dotnet10 - c#

Pangamma | PRO | 01/25/26 07:16:17 PM UTC (Edited) | 0 ⭐ | 15543 👁️ | Never ⏰ | [lrucache]
C# |

3.59 KB

|

Source Code

|

0 👍

/

0 👎

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}";
        }
    }
 
}

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎