我决定在我们的一个应用程序中实现缓存外观 - 目的是最终减少网络开销并限制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();
我正在考虑第一个选项,因为第二个选项需要更多的处理时间 - 另一方面,第二个选项强制执行与所有生成的键完全相同的“长度”。
假设第一个选项将为使用复杂参数类型的方法生成唯一键是否安全?或者可能有一种完全不同的方式吗?
非常感谢帮助和意见!
答案 0 :(得分:4)
根据我发现here和here的一些非常有用的链接,我决定实现或多或少,如下所示:
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.Windsor
或system.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个字符之间的字符串的哈希值的总时间。但无论确切的数字如何,使用加密安全散列函数作为密钥只会减慢您的程序。
如果您愿意,我可以发布我的比较源代码。