使用对象作为键时.Net MemoryCache Miss

.Net MemoryCache Miss when using Objects as Keys

IMemoryCacheobject 一起使用时,TryGetValue 总是失误。我正在尝试将 tuple<string, object> 作为密钥,而 tuple<string, string> 工作得很好。

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

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

我正在使用的对象内部有列表列表,而不是没有 dictionary/set(没有随机排序的东西)。

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

MemoryCache internally uses a ConcurrentDictionary<object, CacheEntry>, which in turn uses the default comparer for the object type, which performs equality comparisons based on the actual type's overrides of Object.Equals and Object.GetHashCode. In your case, your keys are ValueTuple<string, Query>, whatever your Query class is. ValueTuple<T1,T2>.Equals 如果比较实例的组件与当前实例的组件类型相同,并且组件与当前实例的组件相等,则计算结果为真,相等由每个组件的默认相等比较器。

因此,相等比较的执行方式取决于 Query 类型的实现。如果此类型不覆盖 EqualsGetHashCode,也不实现 IEquatable<T>,则执行引用相等,这意味着您仅在传入查询的同一实例时才获得相等。如果你想改变这种行为,你应该扩展你的 Query class 来实现 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);
        }
    }
}