namespace GFramework.Cqrs.Internal; /// /// 提供基于弱键语义的线程安全缓存。 /// 该缓存用于跨容器复用与 绑定的派生元数据, /// 同时避免静态强引用阻止 collectible 程序集或热重载类型被卸载。 /// /// 缓存键类型。 /// 缓存值类型。 /// /// 该缓存只保证“命中时复用”,不保证“永久保留”。 /// 当键对象被 GC 回收后,条目会自然失效,后续访问会重新计算对应值。 /// 这是 CQRS 运行时在卸载安全与热路径性能之间的显式权衡。 /// internal sealed class WeakKeyCache where TKey : class where TValue : class { private readonly object _gate = new(); private ConditionalWeakTable _entries = new(); /// /// 获取指定键对应的缓存值;若当前未命中,则在锁保护下创建并写入。 /// /// 缓存键。 /// 创建缓存值的工厂方法。 /// 已存在或新创建的缓存值。 /// /// 。 /// 或 返回 。 /// public TValue GetOrAdd(TKey key, Func valueFactory) { ArgumentNullException.ThrowIfNull(key); ArgumentNullException.ThrowIfNull(valueFactory); var entries = Volatile.Read(ref _entries); if (entries.TryGetValue(key, out var cachedValue)) return cachedValue; lock (_gate) { entries = _entries; if (entries.TryGetValue(key, out cachedValue)) return cachedValue; var createdValue = valueFactory(key); ArgumentNullException.ThrowIfNull(createdValue); entries.Add(key, createdValue); return createdValue; } } /// /// 获取指定键对应的缓存值;若当前未命中,则在锁保护下使用附加状态创建并写入。 /// /// 创建缓存值时需要携带的附加状态类型。 /// 缓存键。 /// 创建缓存值时复用的附加状态。 /// 基于键与附加状态创建缓存值的工厂方法。 /// 已存在或新创建的缓存值。 /// /// 。 /// 或 返回 。 /// public TValue GetOrAdd(TKey key, TState state, Func valueFactory) { ArgumentNullException.ThrowIfNull(key); ArgumentNullException.ThrowIfNull(valueFactory); var entries = Volatile.Read(ref _entries); if (entries.TryGetValue(key, out var cachedValue)) return cachedValue; lock (_gate) { entries = _entries; if (entries.TryGetValue(key, out cachedValue)) return cachedValue; var createdValue = valueFactory(key, state); ArgumentNullException.ThrowIfNull(createdValue); entries.Add(key, createdValue); return createdValue; } } /// /// 尝试读取当前缓存中的值,而不触发新的创建逻辑。 /// /// 缓存键。 /// 命中时返回的缓存值。 /// 若命中当前缓存则为 ;否则为 /// public bool TryGetValue(TKey key, out TValue? value) { ArgumentNullException.ThrowIfNull(key); return Volatile.Read(ref _entries).TryGetValue(key, out value); } /// /// 清空当前缓存实例。 /// /// /// 该方法主要服务于测试,便于在同一进程内隔离不同用例的静态缓存状态。 /// public void Clear() { lock (_gate) { _entries = new ConditionalWeakTable(); } } /// /// 返回指定键当前命中的缓存对象;若未命中则返回 。 /// /// 缓存键。 /// 当前缓存对象,或 /// /// 该入口仅用于测试通过反射观察缓存状态,不应用于运行时代码路径。 /// public TValue? GetValueOrDefaultForTesting(TKey key) { return TryGetValue(key, out var value) ? value : null; } } /// /// 提供以两段 为键的弱引用缓存。 /// 适用于请求/响应或流请求/响应这类组合类型元数据的复用场景。 /// /// 缓存值类型。 /// /// 第一层和第二层键都使用弱键缓存,因此只要任一类型不再被外部引用, /// 对应条目都允许被 GC 清理,并在后续首次访问时重新建立。 /// internal sealed class WeakTypePairCache where TValue : class { private readonly WeakKeyCache> _entries = new(); /// /// 获取指定类型对对应的缓存值;若未命中则创建并写入。 /// /// 第一段类型键。 /// 第二段类型键。 /// 创建缓存值的工厂方法。 /// 已存在或新创建的缓存值。 /// /// 或 /// 。 /// public TValue GetOrAdd(Type primaryType, Type secondaryType, Func valueFactory) { ArgumentNullException.ThrowIfNull(primaryType); ArgumentNullException.ThrowIfNull(secondaryType); ArgumentNullException.ThrowIfNull(valueFactory); var secondaryEntries = _entries.GetOrAdd(primaryType, static _ => new WeakKeyCache()); return secondaryEntries.GetOrAdd( secondaryType, (PrimaryType: primaryType, Factory: valueFactory), static (cachedSecondaryType, state) => state.Factory(state.PrimaryType, cachedSecondaryType)); } /// /// 尝试读取指定类型对的缓存值,而不触发新的创建逻辑。 /// /// 第一段类型键。 /// 第二段类型键。 /// 命中时返回的缓存值。 /// 若命中当前缓存则为 ;否则为 /// /// 。 /// public bool TryGetValue(Type primaryType, Type secondaryType, out TValue? value) { ArgumentNullException.ThrowIfNull(primaryType); ArgumentNullException.ThrowIfNull(secondaryType); if (_entries.TryGetValue(primaryType, out var secondaryEntries) && secondaryEntries is not null) return secondaryEntries.TryGetValue(secondaryType, out value); value = null; return false; } /// /// 清空当前缓存实例。 /// /// /// 该方法主要服务于测试,避免同一进程里的静态缓存污染后续断言。 /// public void Clear() { _entries.Clear(); } /// /// 返回指定类型对当前命中的缓存对象;若未命中则返回 。 /// /// 第一段类型键。 /// 第二段类型键。 /// 当前缓存对象,或 /// /// 该入口仅用于测试通过反射观察缓存状态,不应用于运行时代码路径。 /// public TValue? GetValueOrDefaultForTesting(Type primaryType, Type secondaryType) { return TryGetValue(primaryType, secondaryType, out var value) ? value : null; } }