基于方法名称和参数值缓存键构造

时间:2012-01-07 13:44:55

标签: c# caching hash dictionary

我决定在我们的一个应用程序中实现缓存外观 - 目的是最终减少网络开销并限制db命中量。我们使用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();

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

假设第一个选项将为使用复杂参数类型的方法生成唯一键是否安全?或者可能有一种完全不同的方式吗?

非常感谢帮助和意见!

2 个答案:

答案 0 :(得分:4)

根据我发现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集合 - 必须在集合的每个元素上应用相同的规则。我可以分享完整的代码。

答案 1 :(得分:2)

您似乎跳过的选项是使用内置GetHashCode()函数的.NET作为字符串。我很确定这是在C#字典中幕后发生的事情,字符串为<TKey>(我之所以提到,因为你用字典标记了问题)。我不确定.NET字典类与你提到的Castle.Windsorsystem.runtime.caching接口有什么关系。

您不希望将GetHashCode用作散列键的原因是MicroSoft特别声明该功能在没有警告的情况下在版本之间进行更改(如提供更独特或更快的执行功能)。如果这个缓存严格地存在于内存中,那么这不是一个问题,因为升级.NET框架将需要重新启动应用程序,擦除缓存。

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

如果最终将MD5或Sha256的字符串输入字典键,则程序可能会在幕后重新连接字符串。自从我读到Dictionary类的内部工作原理以来已经有一段时间了。如果将它保留为Dictionary<String, IEnumerable<MyObject>>(而不是使用int返回值作为键在字符串上调用GetHashCode()),那么字典应该处理哈希代码本身的冲突。

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

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