为IService接口的GetDescriptorsByGroup方法实现缓存方案
解决方案
要实现GetDescriptorsByGroup的懒加载缓存,同时避免在所有IService实现类中重复编写逻辑、不使用基类,最佳方案是基于接口默认方法+ConditionalWeakTable实现实例级安全缓存,既解决内存泄漏问题,又让所有实现类自动复用缓存逻辑。
具体实现
1. 改造IService接口
替换原有的GetDescriptorsByGroup默认方法,加入缓存逻辑:
using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; public interface IService { IReadOnlyList<Descriptor> GetDescriptors(); // 保留原有辅助方法 IEnumerable<Descriptor> GetDescriptorsByName(string name) => GetDescriptors().Where(e => e.Name == name); // 带懒加载缓存的分组查询 IEnumerable<Descriptor> GetDescriptorsByGroup(string group) { // 用ConditionalWeakTable存储每个IService实例的分组缓存,避免内存泄漏 static readonly ConditionalWeakTable<IService, IReadOnlyDictionary<string, IEnumerable<Descriptor>>> _groupCache = new(); // 首次调用时构建缓存,后续直接读取 var groupMap = _groupCache.GetValue(this, service => service.GetDescriptors() .GroupBy(d => d.Group) .ToDictionary(g => g.Key, g => (IEnumerable<Descriptor>)g.ToList(), StringComparer.OrdinalIgnoreCase)); // 返回指定分组结果,无匹配时返回空集合 return groupMap.TryGetValue(group, out var descriptors) ? descriptors : Enumerable.Empty<Descriptor>(); } }
2. 核心逻辑说明
ConditionalWeakTable的优势:这是.NET提供的弱引用字典,当IService实例被GC回收时,对应的缓存条目会自动清理,不会造成内存泄漏,比普通静态字典更安全。- 懒加载特性:只有第一次调用
GetDescriptorsByGroup时,才会执行GroupBy分组操作并缓存结果,后续调用直接从缓存字典取值,完全符合"结果永不改变"的需求。 - 无侵入性:所有已实现
IService的类(包括ServiceA、ServiceB和Mock类)无需任何修改,自动继承该带缓存的方法。 - 线程安全:
ConditionalWeakTable.GetValue本身是线程安全的,多线程同时调用时只会执行一次缓存构建逻辑。
3. 调用验证
原有调用逻辑无需修改,缓存会自动生效:
internal class Program { static void Main(string[] args) { IService service = new ServiceA(); // 第一次调用:触发缓存构建 var group1 = service.GetDescriptorsByGroup("Group1"); // 第二次调用:直接读取缓存 var group1Again = service.GetDescriptorsByGroup("Group1"); } }
备选方案:扩展方法实现缓存
如果不想修改原接口,也可以用扩展方法实现,同样搭配ConditionalWeakTable避免内存泄漏:
public static class IServiceExtensions { private static readonly ConditionalWeakTable<IService, IReadOnlyDictionary<string, IEnumerable<Descriptor>>> _groupCache = new(); public static IEnumerable<Descriptor> GetDescriptorsByGroup(this IService service, string group) { var groupMap = _groupCache.GetValue(service, s => s.GetDescriptors() .GroupBy(d => d.Group) .ToDictionary(g => g.Key, g => (IEnumerable<Descriptor>)g.ToList(), StringComparer.OrdinalIgnoreCase)); return groupMap.TryGetValue(group, out var descriptors) ? descriptors : Enumerable.Empty<Descriptor>(); } // 保留原有扩展方法 public static IEnumerable<Descriptor> GetDescriptorsCount(this IService service, int count) => service.GetDescriptors().Where(e => e.Count == count); public static IEnumerable<string> GetAllDependencies(this IService service) => service.GetDescriptors().SelectMany(e => e.Dependencies); }
这种方式需要删除原接口中的GetDescriptorsByGroup默认方法,让扩展方法覆盖实现,同样无需修改任何IService实现类。
内容的提问来源于stack exchange,提问作者Vastar
相关产品推荐
相关产品推荐

