如何在C#中实现无额外字段的集中式缓存属性?
简化C#懒加载缓存属性并支持全局重置的实现方案
问题背景
你当前使用的懒加载缓存属性模式如下,通过私有字段配合??=运算符实现首次访问时构造对象并复用引用:
public static class MyStaticClass { private MyObject1 _firstObject = null; public MyObject1 FirstObject => _firstObject ??= constructFirstObject(); private MyObject2 _secondObject = null; public MyObject2 SecondObject => _secondObject ??= constructSecondObject(); private MyObject3 _thirdObject = null; public MyObject3 ThirdObject => _thirdObject ??= constructThirdObject(); ... }
该模式的核心是延迟初始化+单例复用,但存在两个痛点:
- 缓存字段与属性一一对应,代码冗余,维护成本高
- 缓存分散在各个私有字段中,无法全局统一重置
你期望简化为类似以下的语法,但直接传构造好的对象会导致每次访问都重建实例,而手动传入标识字符串又过于繁琐:
public static class MyStaticClass { public MyObject1 FirstObject => Cached(constructFirstObject()); public MyObject2 SecondObject => Cached(constructSecondObject()); ... }
解决方案
通过延迟执行委托+自动获取调用者名称+集中缓存容器的组合,可以完美实现你的需求:
1. 全局缓存管理类(线程安全)
利用ConcurrentDictionary实现线程安全的缓存存储,结合CallerMemberName特性自动获取属性名作为缓存键,无需手动传入标识:
using System.Runtime.CompilerServices; using System.Collections.Concurrent; public static class CacheManager { // 线程安全的缓存容器 private static readonly ConcurrentDictionary<string, object> _cachedInstances = new ConcurrentDictionary<string, object>(); /// <summary> /// 获取或创建缓存实例,自动以调用者名称作为缓存键 /// </summary> /// <typeparam name="T">实例类型</typeparam> /// <param name="factory">实例构造委托(仅缓存不存在时执行)</param> /// <param name="callerName">自动传入的调用者名称(无需手动赋值)</param> /// <returns>缓存的实例</returns> public static T GetOrAdd<T>(Func<T> factory, [CallerMemberName] string callerName = null) { if (string.IsNullOrWhiteSpace(callerName)) throw new ArgumentNullException(nameof(callerName)); // GetOrAdd确保线程安全,仅当键不存在时执行factory构造实例 return (T)_cachedInstances.GetOrAdd(callerName, _ => factory()); } /// <summary> /// 清空所有缓存实例 /// </summary> public static void ResetAllCaches() { _cachedInstances.Clear(); } }
2. 业务类简化实现
在你的静态类中,直接调用CacheManager.GetOrAdd并传入构造委托即可,语法完全符合你的预期:
public static class MyStaticClass { public static MyObject1 FirstObject => CacheManager.GetOrAdd(constructFirstObject); public static MyObject2 SecondObject => CacheManager.GetOrAdd(constructSecondObject); public static MyObject3 ThirdObject => CacheManager.GetOrAdd(constructThirdObject); // 原构造方法保持不变 private static MyObject1 constructFirstObject() => new MyObject1(); private static MyObject2 constructSecondObject() => new MyObject2(); private static MyObject3 constructThirdObject() => new MyObject3(); }
3. 关键特性说明
- 延迟执行:传入的
Func<T>委托仅在缓存不存在时才会执行构造逻辑,避免每次访问创建新对象 - 自动键生成:
CallerMemberName特性自动将属性名(如FirstObject)作为缓存键,无需手动编写标识字符串 - 集中管理:所有缓存实例存储在
CacheManager的全局容器中,调用ResetAllCaches()即可一键清空所有缓存 - 线程安全:
ConcurrentDictionary自带线程安全机制,多线程访问时无需额外加锁
扩展优化
如果需要按类别分组重置缓存(而非全局清空),可以给CacheManager添加分组参数,例如:
public static T GetOrAdd<T>(string groupKey, Func<T> factory, [CallerMemberName] string callerName = null) { var fullKey = $"{groupKey}_{callerName}"; return (T)_cachedInstances.GetOrAdd(fullKey, _ => factory()); } public static void ResetGroupCaches(string groupKey) { var keysToRemove = _cachedInstances.Keys.Where(k => k.StartsWith($"{groupKey}_")).ToList(); foreach (var key in keysToRemove) { _cachedInstances.TryRemove(key, out _); } }
使用时指定分组:
public static MyObject1 FirstObject => CacheManager.GetOrAdd("MyStaticClass", constructFirstObject);
内容的提问来源于stack exchange,提问作者Peter
相关产品推荐
相关产品推荐

