You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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();

    ...
}

该模式的核心是延迟初始化+单例复用,但存在两个痛点:

  1. 缓存字段与属性一一对应,代码冗余,维护成本高
  2. 缓存分散在各个私有字段中,无法全局统一重置

你期望简化为类似以下的语法,但直接传构造好的对象会导致每次访问都重建实例,而手动传入标识字符串又过于繁琐:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 12:04:54