浅谈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语法糖的作用时刻在函数末尾,会带来误导。
相关文章
|
11月前
|
缓存 JavaScript C++
【Vue】—计算属性缓存VS方法以及侦听器的区别
【Vue】—计算属性缓存VS方法以及侦听器的区别
|
JavaScript 前端开发
“Vue进阶:深入理解插值、指令、过滤器、计算属性和监听器“
“Vue进阶:深入理解插值、指令、过滤器、计算属性和监听器“
44 0
|
1月前
|
缓存 监控 JavaScript
|
5月前
Vue02基础语法(插值、指令、过滤器、计算属性与监听属性)
Vue02基础语法(插值、指令、过滤器、计算属性与监听属性)
31 0
|
6月前
|
缓存 JavaScript 开发者
什么是vue的计算属性?为什么使用?计算属性和方法有什么区别?怎样选择
什么是vue的计算属性?为什么使用?计算属性和方法有什么区别?怎样选择
56 0
|
6月前
|
移动开发 JavaScript 前端开发
纯原生——旋转的加载效果1
纯原生——旋转的加载效果1
50 0
|
6月前
|
JavaScript C++
从OpenGL渲染的角度排查 creator native 局部换肤的问题
从OpenGL渲染的角度排查 creator native 局部换肤的问题
95 0
|
11月前
|
缓存 JavaScript
Vue基础语法(插值、指令、过滤器、计算属性与监听属性)
Vue基础语法(插值、指令、过滤器、计算属性与监听属性)
73 0
|
缓存 监控 JavaScript
Vue的插值、指令、过滤器、计算属性及监听属性,包涵所有基础技术
Vue的插值、指令、过滤器、计算属性及监听属性,包涵所有基础技术
71 0
|
缓存 JavaScript 前端开发
【Vue入门】语法 —— 插值、指令、过滤器、计算属性、监听器
【Vue入门】语法 —— 插值、指令、过滤器、计算属性、监听器
61 0