重构并删除此记忆代码中的重复内容
我试图从这段代码中删除一些重复项,并让它轻松支持具有更多参数的函数。
您将如何改进这段代码并允许更复杂的功能?
另外,我担心我的密钥生成,某些对象不会明确序列化为字符串,而只是返回它们的类型名,而不是唯一值。建议?
编辑:我使用了 ChaosPandion 的答案,并将其归结为
using System;
using System.Web.Caching;
public static class Memoize
{
public static Cache LocalCache = System.Web.HttpRuntime.Cache ?? System.Web.HttpContext.Current.Cache;
public static TResult ResultOf<TArg1, TResult>(Func<TArg1, TResult> func, long durationInSeconds, TArg1 arg1)
{
var key = HashArguments(func.Method.Name, arg1);
return ResultOf(key, durationInSeconds, () => func(arg1));
}
public static TResult ResultOf<TArg1, TArg2, TResult>(Func<TArg1, TArg2, TResult> func, long durationInSeconds, TArg1 arg1, TArg2 arg2)
{
var key = HashArguments(func.Method.Name, arg1, arg2);
return ResultOf(key, durationInSeconds, () => func(arg1, arg2));
}
public static TResult ResultOf<TArg1, TArg2, TArg3, TResult>(Func<TArg1, TArg2, TArg3, TResult> func, long durationInSeconds, TArg1 arg1, TArg2 arg2, TArg3 arg3)
{
var key = HashArguments(func.Method.Name, arg1, arg2, arg3);
return ResultOf(key, durationInSeconds, () => func(arg1, arg2, arg3));
}
public static TResult ResultOf<TArg1, TArg2, TArg3, TArg4, TResult>(Func<TArg1, TArg2, TArg3, TArg4, TResult> func, long durationInSeconds, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4)
{
var key = HashArguments(func.Method.Name, arg1, arg2, arg3, arg4);
return ResultOf(key, durationInSeconds, () => func(arg1, arg2, arg3, arg4));
}
private static TResult ResultOf<TResult>(string key, long durationInSeconds, Func<TResult> func)
{
return LocalCache.Get(key) != null
? (TResult)LocalCache.Get(key)
: CacheResult(key, durationInSeconds, func());
}
public static void Reset()
{
var enumerator = LocalCache.GetEnumerator();
while (enumerator.MoveNext())
LocalCache.Remove(enumerator.Key.ToString());
}
private static T CacheResult<T>(string key, long durationInSeconds, T value)
{
LocalCache.Insert(key, value, null, DateTime.Now.AddSeconds(durationInSeconds), new TimeSpan());
return value;
}
private static string HashArguments(params object[] args)
{
if (args == null)
return "noargs";
var result = 23;
for (var i = 0; i < args.Length; i++)
{
var arg = args[i];
if (arg == null)
{
result *= (31 * i + 1);
continue;
}
result *= (31 * arg.GetHashCode());
}
return result.ToString();
}
}
I'm trying to remove some duplication from this code, and have it easily support functions with more parameters.
How would you improve this code and allow for more complex functions?
Also, I'm worried about my key generation, some objects won't serialize to a string distinctly, and just return their typename, not a unique value. Suggestions?
Edit: I've used ChaosPandion's answer, and got it down to this
using System;
using System.Web.Caching;
public static class Memoize
{
public static Cache LocalCache = System.Web.HttpRuntime.Cache ?? System.Web.HttpContext.Current.Cache;
public static TResult ResultOf<TArg1, TResult>(Func<TArg1, TResult> func, long durationInSeconds, TArg1 arg1)
{
var key = HashArguments(func.Method.Name, arg1);
return ResultOf(key, durationInSeconds, () => func(arg1));
}
public static TResult ResultOf<TArg1, TArg2, TResult>(Func<TArg1, TArg2, TResult> func, long durationInSeconds, TArg1 arg1, TArg2 arg2)
{
var key = HashArguments(func.Method.Name, arg1, arg2);
return ResultOf(key, durationInSeconds, () => func(arg1, arg2));
}
public static TResult ResultOf<TArg1, TArg2, TArg3, TResult>(Func<TArg1, TArg2, TArg3, TResult> func, long durationInSeconds, TArg1 arg1, TArg2 arg2, TArg3 arg3)
{
var key = HashArguments(func.Method.Name, arg1, arg2, arg3);
return ResultOf(key, durationInSeconds, () => func(arg1, arg2, arg3));
}
public static TResult ResultOf<TArg1, TArg2, TArg3, TArg4, TResult>(Func<TArg1, TArg2, TArg3, TArg4, TResult> func, long durationInSeconds, TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4)
{
var key = HashArguments(func.Method.Name, arg1, arg2, arg3, arg4);
return ResultOf(key, durationInSeconds, () => func(arg1, arg2, arg3, arg4));
}
private static TResult ResultOf<TResult>(string key, long durationInSeconds, Func<TResult> func)
{
return LocalCache.Get(key) != null
? (TResult)LocalCache.Get(key)
: CacheResult(key, durationInSeconds, func());
}
public static void Reset()
{
var enumerator = LocalCache.GetEnumerator();
while (enumerator.MoveNext())
LocalCache.Remove(enumerator.Key.ToString());
}
private static T CacheResult<T>(string key, long durationInSeconds, T value)
{
LocalCache.Insert(key, value, null, DateTime.Now.AddSeconds(durationInSeconds), new TimeSpan());
return value;
}
private static string HashArguments(params object[] args)
{
if (args == null)
return "noargs";
var result = 23;
for (var i = 0; i < args.Length; i++)
{
var arg = args[i];
if (arg == null)
{
result *= (31 * i + 1);
continue;
}
result *= (31 * arg.GetHashCode());
}
return result.ToString();
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这是我的版本,它消除了相当多的重复,并且应该产生更可靠的密钥范围。
编辑
我的最新版本产生了一个非常可靠的发行版。看一下我放置在
Main
方法中的示例。Here is my version which removes quite a bit of duplication and should produce a much more reliable range of keys.
EDIT
My latest version produces a pretty reliable distribution. Take a look at the example I placed in the
Main
method.ChaosPandion 代码的基本思想是正确的,但是为了使其可靠地工作,必须修复一些致命的缺陷。
1)您需要创建一个真正唯一的密钥,而不是使用哈希值的哈希值。一种方法是使用分隔符连接参数数组中每个元素的字符串表示形式。这样,一旦结果被记忆,就会一致地检索它,而不会出现误报。
Chaos 的方法 GUID 技巧在这里很有用,但这避免了依赖
GetHashCode
真正唯一以及依靠相当简单的哈希值来保持唯一的问题。相反,完整的字符串将被散列,但将进行完整的逐字符比较以排除不匹配。诚然,连接一个大字符串并不是特别优雅,甚至可能会影响性能。此外,这仍然给您留下了 ToString 不提供特定于实例的信息的类的问题。然而,有一些基于反射的解决方案。
2) 一个非常简单的优化是在
Complete
中仅调用一次LocalCache.Get
。这是一项潜在昂贵的操作,因此没有理由将成本加倍。除此之外,请遵循 ChaosPandion 的建议。
The basic idea in ChaosPandion's code is correct, but there are some fatal flaws that must be fixed in order for this to work reliably.
1) Rather than using a hash of hashes, you need to make a genuinely unique key. One way would be to concatenate the string representations of each element in the parameter array, with a delimiter. This way, once a result has been memoized, it will consistently be retrieved, without false positives.
Chaos' trick with the GUID of the method is useful here, but this avoids the problem of depending on
GetHashCode
being truly unique as well as counting on a fairly simple hash of hashes to remain unique. Instead, the full string will get hashed, but there will be a full character-by-character comparison to rule out mismatches.Admittedly, concatenating a big string is not particularly elegant and might even impact performance. Moreover, this still leaves you with the issue of classes whose ToString does not give instance-specific information. However, there are solutions for this based on reflection.
2) A very simple optimization would be to call
LocalCache.Get
just once inComplete
. This is a potentially expensive operation, so there's really no excuse for doubling the cost.Other than that, go with what ChaosPandion suggested.