如何重写ConcurrentDictionary的GetOrAdd调用以避免闭包分配
问题原因
原写法中
GetOrAdd传入的lambda捕获了方法作用域的expiration参数,编译器会生成匿名闭包类,每次调用方法都会产生不必要的堆分配,这就是Rider提示闭包分配问题的来源。
单参数场景(仅传expiration)的重写方案
直接使用你提到的带TArg的GetOrAdd重载,把额外参数通过第三个参数传入,lambda的第二个参数接收这个值,完全避免捕获外部变量:
public static ICacheManager GetCacheManager(string cacheManagerName, TimeSpan? expiration) { var cacheManager = CacheManagers.GetOrAdd( key: cacheManagerName, valueFactory: static (keyName, exp) => { var ret = ServiceLocator.Current.GetInstance<ICacheManager>(); if (exp.HasValue) { ret.Configure(keyName, exp.Value); } else { ret.Configure(keyName); } return ret; }, factoryArgument: expiration ); return cacheManager; }
C# 9及以上版本可以给lambda加static修饰,强制编译器检查lambda是否捕获了外部变量,从语法层面避免不小心写出闭包代码,无兼容需求时推荐加上。
多参数场景的处理方案
如果需要传递多个额外参数,直接用值元组(ValueTuple) 作为TArg即可,值元组是值类型,不会产生额外堆分配:
// 示例场景:需要额外传递数据库连接字符串、查询超时时间两个参数 public static ICacheManager GetCacheManager(string cacheManagerName, TimeSpan? expiration, string dbConnStr, int queryTimeout) { var cacheManager = CacheManagers.GetOrAdd( key: cacheManagerName, valueFactory: static (keyName, args) => { var (exp, connStr, timeout) = args; var ret = ServiceLocator.Current.GetInstance<ICacheManager>(); if (exp.HasValue) { ret.Configure(keyName, exp.Value); } else { ret.Configure(keyName); } // 其他逻辑可以直接使用connStr、timeout参数 return ret; }, // 所有额外参数打包成值元组传入 factoryArgument: (expiration, dbConnStr, queryTimeout) ); return cacheManager; }
如果使用的C#版本低于7.0不支持值元组,自定义只读结构体作为参数载体即可,同样不会产生堆分配:
// 自定义参数结构体 public readonly struct CacheFactoryArgs { public TimeSpan? Expiration { get; } public string DbConnStr { get; } public int QueryTimeout { get; } public CacheFactoryArgs(TimeSpan? expiration, string dbConnStr, int queryTimeout) { Expiration = expiration; DbConnStr = dbConnStr; QueryTimeout = queryTimeout; } } // 调用时传入自定义结构体 public static ICacheManager GetCacheManager(string cacheManagerName, TimeSpan? expiration, string dbConnStr, int queryTimeout) { var args = new CacheFactoryArgs(expiration, dbConnStr, queryTimeout); var cacheManager = CacheManagers.GetOrAdd( cacheManagerName, static (keyName, factoryArgs) => { var ret = ServiceLocator.Current.GetInstance<ICacheManager>(); // 直接使用factoryArgs的属性即可 return ret; }, args ); return cacheManager; }
内容的提问来源于stack exchange,提问作者Raptor
相关产品推荐
相关产品推荐

