【问题标题】:.Net MemoryCache Miss when using Objects as Keys使用对象作为键时.Net MemoryCache Miss
【发布时间】:2019-05-31 09:09:30
【问题描述】:

当使用IMemoryCacheobject 时,TryGetValue 总是错过。我正在尝试使用 tuple<string, object> 作为密钥,而 tuple<string, string> 可以正常工作。

这里的代码总是让我缓存未命中:

_cache.TryGetValue(("typeOfCache", query), out var something);
if(something == null) _cache.CreateEntry(("typeOfCache", query));

我正在使用的对象内部有列表列表,而不是没有字典/集合(没有随机排序的)。

这是一个 .net 错误还是我做错了什么?

【问题讨论】:

    标签: c# .net caching memorycache


    【解决方案1】:

    MemoryCache 在内部使用ConcurrentDictionary<object, CacheEntry>,而后者又使用object 类型的默认比较器,它根据Object.EqualsObject.GetHashCode 的实际类型覆盖执行相等比较。在您的情况下,您的密钥是 ValueTuple<string, Query>,无论您的 Query 类是什么。 ValueTuple<T1,T2>.Equals 如果比较实例的组件与当前实例的组件类型相同,并且组件与当前实例的组件相同,则计算结果为 true,每个组件的相等性由默认相等比较器确定.

    因此,如何执行相等比较取决于您的 Query 类型的实现。如果此类型没有覆盖EqualsGetHashCode,也没有实现IEquatable<T>,则执行引用相等,这意味着您只有在传入同一个查询实例时才会得到相等。如果你想改变这种行为,你应该扩展你的Query类来实现IEquatable<Query>

    我还发现CreateEntry 不会立即将新条目添加到缓存中。 .NET Core 文档稀少得令人失望,所以我没有找到预期的行为;但是,您可以通过调用 Set 来确保添加条目。

    例子:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.Extensions.Caching.Memory;
    
    class Program
    {
        static void Main(string[] args)
        {
            var query1 = new Query { Parts = { new List<string> { "abc", "def", "ghi" } } };
            var query2 = new Query { Parts = { new List<string> { "abc", "def", "ghi" } } };
    
            var memoryCache = new MemoryCache(new MemoryCacheOptions());
            memoryCache.Set(("typeOfCache", query1), new object());
            var found = memoryCache.TryGetValue(("typeOfCache", query2), out var something);
            Console.WriteLine(found);
        }
    
        public class Query : IEquatable<Query>
        {
            public List<List<string>> Parts { get; } = new List<List<string>>();
    
            public bool Equals(Query other)
            {
                if (ReferenceEquals(this, other)) return true;
                if (ReferenceEquals(other, null)) return false;
                return this.Parts.Length == other.Parts.Length 
                    && this.Parts.Zip(other.Parts, (x, y) => x.SequenceEqual(y)).All(b => b);
            }
    
            public override bool Equals(object obj)
            {
                return Equals(obj as Query);
            }
    
            public override int GetHashCode()
            {
                return this.Parts.SelectMany(p => p).Take(10).Aggregate(17, (acc, p) => acc * 23 + p?.GetHashCode() ?? 0);
            }
        }
    }    
    

    【讨论】:

    • 啊,现在说得通了。我将确保对象覆盖 Equals 或简单地序列化它们。非常感谢。
    • 欢迎。不要忘记覆盖GetHashCode;字典使用它进行查找。两个相等的对象必须返回相同的哈希值。
    • @Douglas - 你有没有机会用所有的 LINQ 来揭开你在 GetHashCode 中所做的事情的神秘面纱,以及它如何确保 Query 的两个实例在它们的 Part 值相等时生成相同的哈希码?
    • @bubbleking:GetHashCode 方法结合了扁平化Parts 列表中前十个元素的哈希码。 Aggregate 运算符使用流行的模式来组合使用素数的多个哈希码;见this answer
    • 那么,为什么是 10?即使它们中的第 11 个(或更高)元素不相同,这是否会导致为具有 11 个以上元素的两个集合生成相同的哈希码?
    猜你喜欢
    • 2015-03-04
    • 2011-12-12
    • 2015-05-13
    • 2022-01-25
    • 2015-03-26
    • 2018-06-08
    • 1970-01-01
    • 2016-09-08
    • 2013-08-06
    相关资源
    最近更新 更多