using System;
using System.Collections.Generic;
using System.Threading;
public class CacheItem<TValue>
{
public TValue Value { get; set; }
public DateTime ExpiryTime { get; set; }
}
public class GenericCache<TKey, TValue>
{
private readonly Dictionary<TKey, CacheItem<TValue>> _cache;
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
public GenericCache()
{
_cache = new Dictionary<TKey, CacheItem<TValue>>();
}
public void Add(TKey key, TValue value, TimeSpan expirationTime)
{
_lock.EnterWriteLock();
try
{
var cacheItem = new CacheItem<TValue>
{
Value = value,
ExpiryTime = DateTime.Now + expirationTime
};
_cache[key] = cacheItem;
}
finally
{
_lock.ExitWriteLock();
}
}
public bool TryGet(TKey key, out TValue value)
{
_lock.EnterReadLock();
try
{
if (_cache.TryGetValue(key, out var cacheItem) && cacheItem.ExpiryTime > DateTime.Now)
{
value = cacheItem.Value;
return true;
}
}
finally
{
_lock.ExitReadLock();
}
value = default(TValue);
return false;
}
public void RemoveExpiredItems()
{
_lock.EnterWriteLock();
try
{
var keysToRemove = new List<TKey>();
foreach (var kvp in _cache)
{
if (kvp.Value.ExpiryTime < DateTime.Now)
{
keysToRemove.Add(kvp.Key);
}
}
foreach (var key in keysToRemove)
{
_cache.Remove(key);
}
}
finally
{
_lock.ExitWriteLock();
}
}
}
类型参数约束的考虑
TKey
约束:
TKey
通常应该实现IEquatable<TKey>
接口,因为字典需要比较键是否相等。在C#中,大多数内置类型(如int
、string
等)已经实现了此接口。如果TKey
是自定义类型,应该确保该类型正确实现IEquatable<TKey>
接口,以保证字典操作的正确性。
- 也可以考虑
TKey
实现IComparable<TKey>
接口,这样在某些需要对键进行排序的场景下会很有用,虽然在当前简单缓存实现中未用到排序。
TValue
约束:
- 一般情况下不需要对
TValue
进行特殊约束,除非缓存的操作需要TValue
类型具备特定的行为或接口。例如,如果需要对缓存的对象进行序列化,TValue
类型应该实现ISerializable
接口或支持其他序列化机制。
多线程环境下的线程安全性
- 对象实例化:在多线程环境下实例化
GenericCache<TKey, TValue>
对象本身通常是线程安全的,因为实例化过程只涉及内存分配和初始化字段,而C#的对象初始化是原子操作。不过,如果有静态字段或共享资源在对象实例化过程中被访问或修改,就需要特别处理,例如使用lock
关键字或Lazy<T>
类型进行延迟初始化。
- 缓存操作:
- 读写锁机制:使用
ReaderWriterLockSlim
类来控制对缓存字典的访问。对于读操作(TryGet
方法),使用读锁,允许多个线程同时读取缓存,提高并发性能。对于写操作(Add
和RemoveExpiredItems
方法),使用写锁,确保同一时间只有一个线程可以修改缓存,避免数据竞争。
- 锁的粒度:在实现中,对每个方法使用锁来保护整个缓存操作,保证操作的原子性。这样可以防止在读取或修改缓存数据时,其他线程干扰导致数据不一致。
- 异常处理:在获取锁后,使用
try - finally
块确保无论是否发生异常,锁都会被正确释放,防止死锁的发生。