Common.Cache.MemoryCache 3.1.0

dotnet add package Common.Cache.MemoryCache --version 3.1.0
                    
NuGet\Install-Package Common.Cache.MemoryCache -Version 3.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Common.Cache.MemoryCache" Version="3.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Common.Cache.MemoryCache" Version="3.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Common.Cache.MemoryCache" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Common.Cache.MemoryCache --version 3.1.0
                    
#r "nuget: Common.Cache.MemoryCache, 3.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Common.Cache.MemoryCache@3.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Common.Cache.MemoryCache&version=3.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Common.Cache.MemoryCache&version=3.1.0
                    
Install as a Cake Tool

Common.Cache.MemoryCache

Common.Cache.MemoryCache 是一个基于 Microsoft.Extensions.Caching.Memory 的轻量封装,提供统一的缓存读写、批量删除和按模式删除能力。

功能特性

  • 基于 ASP.NET Core IMemoryCache
  • 支持字符串、对象、集合等多种缓存值
  • 支持 GetOrCreateAsync
  • 支持批量删除和按通配符删除
  • 支持获取当前由 Provider 管理的全部缓存键
  • 支持 .NET 6.0 / 7.0 / 8.0 / 9.0 / 10.0

安装

Install-Package Common.Cache.MemoryCache

或:

dotnet add package Common.Cache.MemoryCache

服务注册

推荐使用 AddMemoryCacheStore

services.AddMemoryCacheStore(options =>
{
    options.DefaultExpiry = TimeSpan.FromSeconds(30);
    options.CacheEmptyCollections = false;
    options.FailThrowException = true; // 缓存操作失败时是否抛出异常(默认为true)
});

旧方法 AddMemoryCacheExtension 已标记为过时,但仍可继续使用:

services.AddMemoryCacheExtension(options =>
{
    options.DefaultExpiry = TimeSpan.FromSeconds(30);
    options.CacheEmptyCollections = false;
    options.FailThrowException = true; // 缓存操作失败时是否抛出异常(默认为true)
});

基本使用

public class MyService
{
    private readonly ICacheProvider _cacheProvider;

    public MyService(ICacheProvider cacheProvider)
    {
        _cacheProvider = cacheProvider;
    }

    public async Task<UserInfo> GetUserAsync(string userId)
    {
        return await _cacheProvider.GetOrCreateAsync(
            $"user:{userId}",
            async () => await LoadUserFromDatabase(userId),
            TimeSpan.FromMinutes(10));
    }
}

读写缓存

await _cacheProvider.SetAsync("name", "azrng", TimeSpan.FromMinutes(5));

var name = await _cacheProvider.GetAsync("name");
var count = await _cacheProvider.GetAsync<int>("count");
var user = await _cacheProvider.GetAsync<UserInfo>("user:1");

GetOrCreateAsync

var count = await _cacheProvider.GetOrCreateAsync(
    "counter",
    () => 0,
    TimeSpan.FromMinutes(1));
var user = await _cacheProvider.GetOrCreateAsync(
    "user:1",
    async () => await LoadUserFromDatabase("1"),
    TimeSpan.FromMinutes(10));

原子计数器

IncrementAsync / DecrementAsync 提供进程内原子计数(内部基于 Interlocked),与 Common.Cache.Redis 的同名方法共享 ICacheProvider 契约,业务代码可在两种实现间无缝切换:

var current = await _cacheProvider.IncrementAsync("visit:count");        // 1
current = await _cacheProvider.IncrementAsync("visit:count", 10);        // 11
current = await _cacheProvider.DecrementAsync("visit:count", 1);         // 10

// 需要过期时间时配合 ExpireAsync
await _cacheProvider.ExpireAsync("visit:count", TimeSpan.FromMinutes(1));

行为说明:

  • key 不存在时从 0 开始累加;已有值不是整数时抛出异常
  • 新建计数器不设置过期时间(不套用 DefaultExpiry,与 Redis INCR 语义对齐),需要过期请显式调用 ExpireAsync
  • 自增/自减不会重建缓存条目,已通过 ExpireAsync 设置的过期时间保持不变
  • 内存实现只保证单节点内的原子性;多实例部署请使用 Common.Cache.Redis 的分布式计数
  • 计数器可通过 GetAsync / GetAsync<long> / ExistAsync / RemoveAsync 正常读取和删除

批量操作

await _cacheProvider.RemoveAsync(new[] { "user:1", "user:2", "user:3" });

await _cacheProvider.RemoveMatchKeyAsync("user:*");

var allKeys = ((IMemoryCacheProvider)_cacheProvider).GetAllKeys();

var allValues = await ((IMemoryCacheProvider)_cacheProvider).GetAllAsync(allKeys);

await ((IMemoryCacheProvider)_cacheProvider).RemoveAllKeyAsync();

配置项

MemoryCacheOptions 提供以下配置:

  • DefaultExpiry: 默认过期时间,默认值为 5 秒
  • CacheEmptyCollections: 是否缓存空集合和空字符串,默认值为 true
  • FailThrowException: 缓存读取/写入等缓存操作失败时是否抛出异常,默认值为 true。业务工厂方法自身抛出的异常始终透出,不受此配置吞掉。

行为说明

1. 合法默认值会被正常缓存

从当前实现开始,0falseDateTime.MinValue 这类合法业务值会被正常缓存,不再被误判为空值。

await _cacheProvider.SetAsync("bool:false", false);
var value = await _cacheProvider.GetAsync<bool>("bool:false"); // false

2. CacheEmptyCollections 只控制空集合和空字符串

CacheEmptyCollections = false 时:

  • null 不会缓存
  • 空字符串不会缓存
  • 空集合不会缓存
  • 0false 等合法默认值仍然会缓存

3. 异常处理可通过配置控制

2.1.0 开始,可通过 FailThrowException 配置控制缓存操作失败时的行为:

  • true(默认):记录日志并抛出异常,避免把真实故障误判成”缓存未命中”
  • false:记录日志后降级处理。从 3.1.0 起,GetOrCreateAsync 缓存读失败时按"未命中"处理,回源执行工厂方法返回真实数据;缓存写失败时不影响已取得的数据返回(3.1.0 之前的版本会直接返回 default,可能把缓存故障放大为业务拿到假数据)

FailThrowException 只控制缓存读取、写入等缓存组件自身异常。GetOrCreateAsync 的工厂方法用于加载业务数据,工厂方法抛出的异常会始终原样透出,避免把数据库、远程服务或业务加载失败误判为缓存未命中。

4. 并发访问同一个 Key 时会做单 Key 同步

同一个 Key 在高并发下首次未命中时,Provider 会按 Key 做同步,避免多个线程同时重复执行工厂方法。

这能减少数据库或远程调用的重复压力。同步锁会在本次同 Key 等待队列结束后释放并清理,避免高基数 Key 长时间运行时持续占用锁对象。

5. RemoveMatchKeyAsync 使用通配符语义

RemoveMatchKeyAsync 不是正则表达式,而是通配符匹配:

  • * 匹配任意多个字符
  • ? 匹配任意单个字符
  • [] 匹配指定字符范围

示例:

await _cacheProvider.RemoveMatchKeyAsync("user:*");
await _cacheProvider.RemoveMatchKeyAsync("order:2026-03-??");

6. GetAllKeys 只返回当前 Provider 管理的键

GetAllKeys 不再通过反射读取 MemoryCache 内部私有字段,而是返回通过当前 Provider 写入并跟踪的键集合。

这意味着:

  • 通过 IMemoryCache 直接写入的键不会出现在 GetAllKeys
  • 通过 Provider 删除、覆盖、驱逐的键会同步更新跟踪集合
  • 实现更稳定,不依赖 .NET 运行时内部结构

7. GetAllAsync 只返回命中的键

GetAllAsync 从 2.1.1 起,只返回实际命中的 key,不存在的 key 不会以 null 值出现在结果字典中。

await provider.SetAsync("ga:1", "v1");
var dict = await ((IMemoryCacheProvider)provider).GetAllAsync(new[] { "ga:1", "ga:missing" });
// dict 只包含 ga:1,不包含 ga:missing

注意事项

  • 不建议使用 IEnumerable<T>IQueryable<T>IAsyncEnumerable<T> 作为 GetOrCreateAsync<T>T
  • 如果需要缓存集合,请优先使用 List<T> 或数组
  • SetAsync<T>(key, null) 会返回 false,不会写入缓存

同时使用内存缓存与 Redis 缓存

Common.Cache.MemoryCacheCommon.Cache.Redis 都会把各自的 Provider 注册到同一个 ICacheProvider 抽象上。由于 ICacheProvider 在依赖注入容器中只能绑定一个实现(TryAdd 不覆盖,先注册者占位),两个包同时注册时,只有一个会真正绑定到 ICacheProvider,另一个对 ICacheProvider 的绑定会被静默忽略。

如果需要在同一个项目里同时使用两种缓存,不要注入 ICacheProvider,改为注入各自的具体接口

// 注册两种缓存(顺序无关)
services.AddMemoryCacheStore();
services.AddRedisCacheStore();

public class MyService
{
    // 直接注入具体接口,两者共存不冲突
    public MyService(IMemoryCacheProvider memoryCache, IRedisProvider redisCache)
    {
        _memoryCache = memoryCache;
        _redisCache = redisCache;
    }
}
  • IMemoryCacheProvider:内存缓存能力(含 GetAllKeysGetAllAsyncRemoveAllKeyAsync 等本地特有方法)
  • IRedisProvider:Redis 缓存能力(含发布订阅等 Redis 特有方法)

注:ICacheProvider 适合"项目只用单一缓存"的场景,作为默认抽象使用。两种缓存共存时,请使用上述具体接口。

版本更新记录

  • 3.1.0
    • 新增IncrementAsync / DecrementAsync 原子计数器(跟随 Azrng.Cache.Core 1.1.0),进程内 Interlocked 原子累加,仅单节点安全;失败始终抛出异常,不受 FailThrowException 影响
    • 行为变更FailThrowException = false 时缓存读失败的降级行为由"返回 default"改为"回源执行工厂方法返回真实数据"(与 Common.Cache.Redis 对齐)。2.1.1 引入的"返回 default"会把缓存故障放大为业务拿到假数据,本次修正;缓存写失败时也不再丢弃已取得的数据
    • 行为变更MemoryCacheProvider / IMemoryCacheProvider / ICacheProvider 注册生命周期由 Scoped 改为 Singleton(Provider 无实例状态,与 Redis 实现一致,单例服务可直接注入)
    • 优化:RemoveMatchKeyAsync 的通配符正则增加 1 秒匹配超时;[ 未闭合等非法模式抛出带原始模式信息的 ArgumentException
    • 依赖升级:Azrng.Cache.Core 1.0.0 → 1.1.0
  • 3.0.0
    • 破坏性更新:跟随 Azrng.Cache.Core 1.0.0,GetAsync(string) 返回 Task<string?>GetAsync<T> 返回 Task<T?>,如实表达未命中返回 null 的语义
    • 移除因旧接口非 null 契约而添加的 !(null-forgiving)
    • 修复GetOrCreateAsync 不再把业务工厂方法异常当作缓存异常吞掉;即使 FailThrowException = false,工厂方法异常也会原样透出
    • 修复:单 Key 并发同步锁在等待队列结束后及时清理,避免高基数 Key 场景下锁对象持续增长
    • 依赖升级:Azrng.Cache.Core 1.0.0
  • 2.1.1
    • 重命名(破坏性)MemoryConfig 重命名为 MemoryCacheOptions,并补全 XML 注释、启用 nullable
      • 迁移指引:将原 Configure<MemoryConfig> / new MemoryConfig() / Action<MemoryConfig> 全部替换为 MemoryCacheOptions
    • 修复:缓存组件异常且 FailThrowException = false 时,GetOrCreateAsync 不再重复调用工厂方法,改为返回 default,对齐文档承诺
    • 修复GetAllAsync 不存在的 key 不再以 null 进入结果字典,改为只返回命中的 key
    • 优化:csproj 补充 PackageLicenseExpressionRepositoryType、符号包(snupkg)等发布元数据
  • 2.1.0
    • 新增FailThrowException 配置项,允许控制缓存操作失败时的行为
      • true(默认):记录日志并抛出异常,与 2.0.0 行为一致
      • false:记录日志并返回默认值,不抛出异常,提供更灵活的错误处理策略
    • 优化GetOrCreateAsync 支持 FailThrowException 配置,统一异常处理行为
  • 2.0.0
    • 破坏性更新GetOrCreateAsync 发生异常时不再吞掉异常并返回 default,改为记录日志后继续抛出异常
    • 破坏性更新RemoveMatchKeyAsync 改为按通配符语义匹配,支持 *?[],不再直接把输入当作正则表达式
    • 破坏性更新GetAllKeys 改为返回当前 Provider 管理并跟踪的键,不再依赖反射读取 MemoryCache 内部私有字段
    • 修复:支持缓存合法默认值,例如 0falseDateTime.MinValue
    • 修复:GetOrCreateAsync 增加单 Key 并发保护,避免并发未命中时重复执行工厂方法
    • 修复:RemoveAsync(IEnumerable<string>) 过滤空 key 和重复 key,并返回更准确的删除数量
    • 优化:依赖注入注册改为复用同一个作用域内的 MemoryCacheProvider 实例
  • 1.3.2
    • 更新异常信息输出
  • 1.3.1
    • 更新批量删除缓存的日志输出
  • 1.3.0
    • 修复 .NET 8 模糊匹配生效问题
    • 引用 .NET 10 正式包
  • 1.3.0-beta9
    • 更新 .NET 10
  • 1.3.0-beta8
    • 修复设置不缓存空值时的问题
  • 1.3.0-beta7
    • 修复 GetOrCreateAsync 读取不到缓存仍写入的问题
  • 1.3.0-beta6
    • 更新命名空间
  • 1.3.0-beta5
    • 支持 .NET 9
    • 移除对 netstandard2.1 的支持
    • 更新注入方法 AddMemoryCacheStore
  • 1.3.0-beta4
    • 修改方法 KeyDeleteInBatchAsyncRemoveMatchKeyAsync
    • 修改方法 GetAllCacheKeysGetAllKeys
    • 修改方法 RemoveCacheAllAsyncRemoveAllKeyAsync
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.1.0 49 7/27/2026
3.0.0 109 7/3/2026
2.1.0 116 3/26/2026
2.0.0 130 3/23/2026
1.3.2 335 12/17/2025
1.3.1 487 12/10/2025
1.3.0 392 11/30/2025
1.3.0-beta9 197 11/6/2025
1.3.0-beta8 227 10/9/2025
1.3.0-beta7 262 8/28/2025
1.3.0-beta5 225 7/14/2025
1.3.0-beta4 438 6/5/2024
1.3.0-beta3 202 2/9/2024
1.3.0-beta2 189 2/8/2024
1.3.0-beta11 314 11/12/2025
1.3.0-beta10 140 11/7/2025
1.3.0-beta1 314 11/14/2022
1.2.0 588 11/12/2022
1.1.1 643 7/9/2022
1.1.0 749 11/11/2020
Loading failed