C#缓存变量对比及代理模式缓存失效问题求助(.NET 4.8)
.NET Framework 4.8 C#缓存变量比较与代理模式缓存问题排查
一、缓存中变量输入内容的比较方式
针对不同类型的变量,缓存中的内容比较逻辑如下:
- 值类型(如DateTime、int、double):直接使用
==运算符或Equals()方法即可,值类型默认比较的是内容本身。 - 引用类型:
- 若需比较引用地址:使用默认的
==运算符(引用类型默认==比较对象引用)。 - 若需比较内容:需重写类的
Equals()和GetHashCode()方法,或实现IEquatable<T>接口,确保比较逻辑符合业务需求。
- 若需比较引用地址:使用默认的
- 字典缓存键的比较:
Dictionary<TKey, TValue>默认使用EqualityComparer<TKey>.Default,值类型无需额外处理;自定义引用类型作为键时,必须确保其Equals和GetHashCode逻辑正确,否则会导致缓存命中失败。
二、代理模式缓存失效的Bug排查与修复
1. 最核心的Bug:代理实例重复创建
你的Main方法中,每次循环都会新建GoldServiceProxy实例,而缓存是代理类的私有字段,每次新实例的缓存都是空的,完全无法复用之前的查询结果。
修复方法:将代理实例移到循环外部,复用同一个实例的缓存:
static void Main(string[] args) { // 复用同一个代理实例,缓存才会生效 GoldServiceProxy goldServiceProxy = new GoldServiceProxy(new GoldService()); for (int i = 0; i < 10; i++) { try { // 注意取DateTime.Now.Date,避免时分秒导致同一日期被视为不同键 var price = goldServiceProxy.get_price(DateTime.Now.Date); Console.WriteLine(price); } catch { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Data nie została znaleziona"); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Wciśnij dowolny klawisz aby zakończyć"); Console.ReadKey(); } } }
2. GoldService.get_price方法的致命问题
- 方法参数
DateTime date完全未被使用,而是从控制台读取日期,导致代理传入的缓存键和实际查询的日期不匹配,缓存逻辑彻底失效。 - 方法始终返回
0,缓存的都是无效值,毫无意义。 - 用字符串拆分解析JSON的方式极易出错,应该使用JSON序列化库(如Newtonsoft.Json)进行解析。
- 同步调用异步方法
GetResponseAsync().Result容易引发死锁,建议改用async/await。
修复后的GoldService示例:
using System.Net.Http; using Newtonsoft.Json; public class GoldService { private const string uriBase = "http://api.nbp.pl/api/cenyzlota/"; private readonly HttpClient _httpClient = new HttpClient(); public async Task<double> GetPriceAsync(DateTime date) { string dateStr = date.ToString("yyyy-MM-dd"); string uriQuery = $"{uriBase}{dateStr}"; var response = await _httpClient.GetAsync(uriQuery); response.EnsureSuccessStatusCode(); string json = await response.Content.ReadAsStringAsync(); var goldData = JsonConvert.DeserializeObject<List<GoldPriceDto>>(json); if (goldData == null || goldData.Count == 0) throw new InvalidOperationException("Nie znaleziono danych dla podanej daty"); return goldData[0].Cena; } private class GoldPriceDto { [JsonProperty("cena")] public double Cena { get; set; } [JsonProperty("data")] public string Data { get; set; } } }
3. 代理类的优化与修复
- 同步方法改为异步,与服务类的方法签名匹配。
- 缓存键使用日期的
Date部分,避免时分秒导致相同日期被视为不同键。 - 添加线程安全锁,防止多线程环境下的缓存竞争问题。
修复后的代理类示例:
internal class GoldServiceProxy { private readonly GoldService _goldService; private readonly Dictionary<DateTime, double> _cache = new Dictionary<DateTime, double>(); private readonly object _lockObj = new object(); public GoldServiceProxy(GoldService goldService) { _goldService = goldService; } public async Task<double> GetPriceAsync(DateTime date) { var cacheKey = date.Date; lock (_lockObj) { if (_cache.TryGetValue(cacheKey, out var cachedPrice)) return cachedPrice; } var price = await _goldService.GetPriceAsync(cacheKey); lock (_lockObj) { if (!_cache.ContainsKey(cacheKey)) _cache.Add(cacheKey, price); } return price; } }
4. 最终的Main方法
static async Task Main(string[] args) { var goldServiceProxy = new GoldServiceProxy(new GoldService()); for (int i = 0; i < 10; i++) { try { var price = await goldServiceProxy.GetPriceAsync(DateTime.Now); Console.WriteLine($"Cena złota: {price} zł"); } catch (HttpRequestException ex) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"Błąd połączenia: {ex.Message}"); } catch (InvalidOperationException ex) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine(ex.Message); } catch { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Nieoczekiwany błąd"); } finally { Console.ResetColor(); } } Console.WriteLine("Naciśnij dowolny klawisz aby zakończyć"); Console.ReadKey(); }
三、缓存变量比较的关键注意事项
- 日期类型作为缓存键时,务必使用
Date属性忽略时分秒,避免同一日期因时间不同被视为不同键。 - 自定义引用类型作为字典键时,必须重写
Equals()和GetHashCode()方法,确保比较逻辑正确。 - 多线程环境下操作缓存,必须添加线程锁,防止并发写入导致的缓存异常或数据不一致。
内容的提问来源于stack exchange,提问作者Tomasz Myszak
相关产品推荐
相关产品推荐

