【问题标题】:Cache key construction based on the method name and argument values基于方法名称和参数值的缓存键构造
【发布时间】:2012-02-04 21:47:10
【问题描述】:

我决定在我们的一个应用程序中实现缓存外观 - 目的是最终减少网络开销并限制数据库命中的数量。我们使用Castle.Windsor 作为我们的IoC Container,我们决定使用Interceptors 在我们的服务层之上使用System.Runtime.Caching 命名空间添加缓存功能。

目前我无法完全弄清楚构建cache key 的最佳方法是什么。目标是区分不同的方法,还包括传递的参数值——这意味着这两个方法调用应该缓存在两个不同的键下:

IEnumerable<MyObject> GetMyObjectByParam(56); // key1
IEnumerable<MyObject> GetMyObjectByParam(23); // key2

目前我可以看到两种可能的实现方式:

选项 1: 组装 |班级 |方法返回类型 |方法名称 |参数类型 |参数哈希码

"MyAssembly.MyClass IEnumerable<MyObject> GetMyObjectByParam(long) { 56 }";

选项 2: MD5 或 SHA-256 根据方法的完全限定名称和传递的参数值计算哈希

string key = new SHA256Managed().ComputeHash(name + args).ToString();

我正在考虑第一个选项,因为第二个选项需要更多处理时间 - 另一方面,第二个选项强制所有生成的密钥的“长度”完全相同。

假设第一个选项将为使用复杂参数类型的方法生成唯一键是否安全?或者也许有完全不同的方式来做到这一点?

我们将非常感谢您的帮助和意见!

【问题讨论】:

    标签: c# caching hash dictionary


    【解决方案1】:

    根据我发现的一些非常有用的链接 herehere,我决定或多或少地实现它,如下所示:

    public sealed class CacheKey : IEquatable<CacheKey>
    {
        private readonly Type reflectedType;
        private readonly Type returnType;
        private readonly string name;
        private readonly Type[] parameterTypes;
        private readonly object[] arguments;
    
        public User(Type reflectedType, Type returnType, string name, 
            Type[] parameterTypes, object[] arguments)
        {
            // check for null, incorrect values etc.
    
            this.reflectedType = reflectedType;
            this.returnType = returnType;
            this.name = name;
            this.parameterTypes = parameterTypes;
            this.arguments = arguments;
        }
    
        public override bool Equals(object obj)
        {
            return Equals(obj as CacheKey);
        }
    
        public bool Equals(CacheKey other)
        {
            if (other == null)
            {
                return false;
            }
    
            for (int i = 0; i < parameterTypes.Count; i++)
            {
                if (!parameterTypes[i].Equals(other.parameterTypes[i]))
                {
                    return false;
                }
            }
    
            for (int i = 0; i < arguments.Count; i++)
            {
                if (!arguments[i].Equals(other.arguments[i]))
                {
                    return false;
                }
            }
    
            return reflectedType.Equals(other.reflectedType) &&
               returnType.Equals(other.returnType) &&
               name.Equals(other.name);
        }
    
        private override int GetHashCode()
        {
            unchecked
            {
                int hash = 17;
    
                hash = hash * 31 + reflectedType.GetHashCode();
                hash = hash * 31 + returnType.GetHashCode();
                hash = hash * 31 + name.GetHashCode();
    
                for (int i = 0; i < parameterTypes.Count; i++)
                {
                    hash = hash * 31 + parameterTypes[i].GetHashCode();
                }
    
                for (int i = 0; i < arguments.Count; i++)
                {
                    hash = hash * 31 + arguments[i].GetHashCode();
                }
    
                return hash;
            }
        }
    }
    

    基本上这只是一个一般性的想法 - 上面的代码可以很容易地重写为更通用的版本,其中包含一个 Fields 集合 - 必须对集合的每个元素应用相同的规则。我可以分享完整的代码。

    【讨论】:

      【解决方案2】:

      您似乎跳过的一个选项是使用 .NET 内置的 GetHashCode() 函数来处理字符串。我相当确定这是在 C# 字典中使用字符串作为 &lt;TKey&gt; 的幕后发生的事情(我提到这一点是因为你已经用字典标记了这个问题)。我不确定 .NET 字典类与您的 Castle.Windsor 或您提到的 system.runtime.caching 接口有何关系。

      您不想将 GetHashCode 用作哈希键的原因是,MicroSoft 明确否认该功能可以在没有警告的情况下在版本之间进行更改(如提供更独特或更快执行的功能)。如果此缓存将严格存在于内存中,则无需担心,因为升级 .NET 框架将需要重新启动应用程序并清除缓存。

      为了澄清,仅使用连接字符串(选项 1)应该足够唯一。看起来您已经添加了所有可能的东西来唯一限定您的方法。

      如果您最终将 MD5 或 Sha256 的字符串输入到字典键中,那么程序可能无论如何都会在幕后重新散列字符串。自从我了解 Dictionary 类的内部工作原理以来已经有一段时间了。如果您将其保留为 Dictionary&lt;String, IEnumerable&lt;MyObject&gt;&gt;(而不是使用 int 返回值作为键自己在字符串上调用 GetHashCode()),那么字典应该处理哈希码本身的冲突。

      还要注意(至少根据我机器上运行的基准程序),MD5 比 SHA1 快 10% 左右,是 SHA256 的两倍。 String.GetHashCode() 比 MD5 快大约 20 倍(它不是加密安全的)。测试了总时间来计算相同的 100,000 个随机生成的长度在 32 到 1024 个字符之间的字符串的哈希值。但不管确切的数字是多少,使用加密安全的哈希函数作为密钥只会减慢您的程序。

      如果您愿意,我可以发布源代码进行比较。

      【讨论】:

      • 嗨,Patrick,我不使用 Option 1 的原因有两个 - 首先,有时我会使用相当大的分层对象,这些对象很难序列化 fast在密钥构造期间足够 - 在其他情况下,我意识到String.GetHashCode 方法实际上返回相同的值并非不可能。这就是为什么我实现了一个类,它正确地 覆盖GetHashCodeEquals 方法并最终解决可能的冲突。我给你一个 +1 引导我走向正确的方向 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 2017-12-13
      • 1970-01-01
      • 2016-06-09
      相关资源
      最近更新 更多