浅谈MemoryCache的原生插值方式

简介: MemoryCache插值的实现过程很奇葩尽量使用带明确大括号范围的using语法,C#8.0推出的不带大括号的using语法糖的作用时刻在函数末尾,会带来误导。

.NET运行时内置了常用的缓存模块:MemoryCache


标准的MemoryCache暴露了如下几个属性和方法:


public int Count { get; }
public void Compact(double percentage);
public ICacheEntry CreateEntry(object key);
public void Dispose();
public void Remove(object key);
public bool TryGetValue(object key, out object result);
protected virtual void Dispose(bool disposing);


但是你使用常规模式去插值/获取值,可能会出现意想不到的情况。


就如下这样的常规代码:


var s = new MemoryCache(new MemoryCacheOptions { });
var entry = s.CreateEntry("WeChatID");
entry.Value = "精益码农";
var f =  s.TryGetValue("WeChatID",out  object obj);
Console.WriteLine(f);
Console.WriteLine(obj);


会输出如下结果:


559448af7274364e224017670c27978a.png


是不是很意外。


但是看官们一般不会使用MemoryCache的原生方法,而是使用位于同一命名空间的 扩展方法Set


var s = new MemoryCache(new MemoryCacheOptions { });
s.Set("WeChatID", "精益码农");
var f = s.TryGetValue("WeChatID", out object obj);
Console.WriteLine(f);
Console.WriteLine(obj);


如此便能正确输出。


b30d80d86846d67e04730847679ed329.png


扩展类源码看一看


public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value)
 {
      using ICacheEntry entry = cache.CreateEntry(key);
      entry.Value = value;
      return value;
}


扩展方法与原生方法的差异在于using关键字 (也说明了CacheEntry继承自IDisposable接口)。


继续追溯CacheEntry实现的Dispose方法:


public void Dispose()
        {
            if (!_state.IsDisposed)
            {
                _state.IsDisposed = true;
                if (_cache.TrackLinkedCacheEntries)
                {
                    CacheEntryHelper.ExitScope(this, _previous);
                }
                // Don't commit or propagate options if the CacheEntry Value was never set.
                // We assume an exception occurred causing the caller to not set the Value successfully,
                // so don't use this entry.
                if (_state.IsValueSet)
                {
                    _cache.SetEntry(this);
                    if (_previous != null && CanPropagateOptions())
                    {
                        PropagateOptions(_previous);
                    }
                }
                _previous = null; // we don't want to root unnecessary objects
            }
        }


注意其中的_cache.SetEntry(this),表示在MemoryCache底层的ConcurrentDictionary<object, CacheEntry>集合插入缓存项,


综上:缓存项CacheEntry需要被Dispose,才能被插入MemoeyCache


这是怎样的设计模式?IDisposable接口不是用来释放资源吗?


为啥要使用Dispose方法来向MemoryCache插值?


不能使用一个明确的Commit方法吗?


这在Github上也有issue讨论,从2017年开始就有大佬质疑这是一个反人类的设计思路,官方为了不引入Break Change,一直保持到现在。


基于此现状,我们如果使用MemoryCache的原生插值方法, 需要这样:


var s = new MemoryCache(new MemoryCacheOptions { });
 using (var entry = s.CreateEntry("WeChatID"))
 {
      entry.Value = "精益码农";
 }
 var f = s.TryGetValue("WeChatID", out object obj);
 ...


尽量不要使用C#8.0推出的不带大括号的using语法


using var entry = s.CreateEntry("WeChatID");
 entry.Value = "精益码农";
 var f = s.TryGetValue("WeChatID", out object obj);
 ...


这种没明确指定using作用范围的语法,会在函数末尾才执行Dispose方法, 导致执行到TryGetValue时,缓存项其实还没插入!!!


Last


  1. MemoryCache插值的实现过程很奇葩


  1. 尽量使用带明确大括号范围的using语法,C#8.0推出的不带大括号的using语法糖的作用时刻在函数末尾,会带来误导。
相关文章
|
2月前
|
存储
ES6中的Set数据结构的常用方法和使用场景
ES6中的Set数据结构的常用方法和使用场景
|
6月前
|
JavaScript Java 数据安全/隐私保护
js对象可扩展性和属性的四个特性(下)
js对象可扩展性和属性的四个特性(下)
|
存储 Java Go
巧用 Go Map 特性对数组或切片去重
本文介绍了如何利用 Go 的复合数据类型 Map 的特性对数组或切片进行去重。值得注意的一个地方是,在使用 Map 构建 Set 时,Value 的数据类型指定为 struct{},原因是后面在添加键值对的时候,指定的 Value 为空结构体 strcut{}{},空结构体不占用内存空间。
939 1
巧用 Go Map 特性对数组或切片去重
Attribute特性的原理
Attribute特性的原理
55 0
c#之Attribute特性的原理
c#之Attribute特性的原理
74 0
|
存储 移动开发 JavaScript
cookie,sessionStorage和localStorage的区别?精灵图是什么?原生对象、内置对象、宿主对象的联系和区别?
cookie:** 直译是"小饼干"的意思,在web中就是存在客户端的“一小块”数据。一个cookie大小不超过4kb,在设置的过期时间过期之后会消失,每次请求都会与服务器交互。
|
前端开发 JavaScript
渐进增强和优雅降级之间的区别?css3动画格式?== 和 === 的区别?
渐进增强:** 一开始就针对低版本浏览器进行构建页面,完成基本的功能,然后再针对高级浏览器进行效果、交互、追加功能达到更好的体验。
|
JSON 安全 程序员
GoFrame的gmap相比Go原生的map,天然支持排序和有序遍历
这篇文章就是给初学的小伙伴们答疑解惑的,会为大家介绍: 为什么Go语言中的map是无序的,如何自定义实现map的排序?
233 0
GoFrame的gmap相比Go原生的map,天然支持排序和有序遍历
|
开发框架 .NET 编译器
C#反射与特性(七): 自定义特性以及应用
C#反射与特性(七): 自定义特性以及应用
299 0
C#反射与特性(七): 自定义特性以及应用