我知道这个话题(记忆)已经讨论了很多(例如here),但是没有答案我可以满足我对DRY原理的满意程度,所以请阅读整个问题并我想讲的三点。
我有一个像这样的简单支持类:
public class Memoized<T1, TResult>
{
private readonly Func<T1, TResult> _f;
private readonly Dictionary<T1, TResult> _cache = new Dictionary<T1, TResult>();
public Memoized(Func<T1, TResult> f)
{
_f = f;
}
public TResult Invoke(T1 p1)
{
if (p1 == null) throw new ArgumentNullException(nameof(p1));
if (_cache.TryGetValue(p1, out var res)) return res;
return _cache[p1] = _f(p1);
}
public static Func<T1, TResult> Of(Func<T1, TResult> f)
{
var memo = new Memoized<T1, TResult>(f);
return x => memo.Invoke(x);
}
}
没什么特别花哨的,但是它允许我这样做:
public class MyClass
{
public Func<int, bool> MemoizedMethod { get; }
private bool UncachedMethod(int v)
{
return v > 0;
}
public MyClass()
{
MemoizedMethod = Memoized<int, bool>.Of(UncachedMethod);
}
}
现在,即使生成的代码不是非常嘈杂,我仍在尝试确定实现是否可以更优雅,因为目前我需要:
任何建议删除上述一个或两个要点的策略都是很好的。
答案 0 :(得分:1)
如果您在lambda中捕获字典,则状态将被隐式维护。
public class Memoized
{
// Example with a single parameter. That parameter becomes the key to the dictionary.
public static Func<T1, TRet> Of<T1, TRet>(Func<T1, TRet> f)
{
ConcurrentDictionary<T1, TRet> cache = new ConcurrentDictionary<T1, TRet>();
return (arg1) => cache.GetOrAdd(arg1, xarg=>f(xarg));
}
// Example with two parameters. The key is a tuple, and it must be unpacked before calling func.
// Three or more parameters generalize from this
public static Func<T1, T2, TRet> Of<T1, T2, TRet>(Func<T1, T2, TRet> f)
{
ConcurrentDictionary<Tuple<T1,T2>, TRet> cache = new ConcurrentDictionary<Tuple<T1, T2>, TRet>();
return (arg1, arg2) => cache.GetOrAdd(new Tuple<T1,T2>(arg1, arg2),
(xarg)=>f(xarg.Item1, xarg.Item2)
);
}
}
用法示例:
class Test
{
public int Method(String s, String s2)
{
return 99;
}
}
class Program
{
static bool UncachedMethod(int x) { return x > 0; }
static void Main(string[] args)
{
var cached = Memoized.Of<int,bool>(UncachedMethod);
var exampleCall = cached(44);
var exampleCall2 = cached(44);
// Capture a non-static member function
var x = new Test();
var cachedX = Memoized.Of<String, String,int>(x.Method);
var exampleCall3 = cachedX("a","b");
}
}
答案 1 :(得分:0)
在追求优雅的过程中,我终于找到了我认为在任何地方都能看到的最佳语法:
PATH
实现(一个很小的扩展类):
private class MemoizedTest
{
private int _counter = 0;
public int Method(int p) => this.Memoized(p, x =>
{
return _counter += x;
});
}
说明
在实现中,我使用namespace System
{
public static class MemoizerExtension
{
internal static ConditionalWeakTable<object, ConcurrentDictionary<string, object>> _weakCache =
new ConditionalWeakTable<object, ConcurrentDictionary<string, object>>();
public static TResult Memoized<T1, TResult>(
this object context,
T1 arg,
Func<T1, TResult> f,
[CallerMemberName] string cacheKey = null)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (cacheKey == null) throw new ArgumentNullException(nameof(cacheKey));
var objCache = _weakCache.GetOrCreateValue(context);
var methodCache = (ConcurrentDictionary<T1, TResult>) objCache
.GetOrAdd(cacheKey, _ => new ConcurrentDictionary<T1, TResult>());
return methodCache.GetOrAdd(arg, f);
}
}
}
进行缓存,有效地扩展了调用备忘录的对象的内部结构。作为附加键,使用ConditionalWeakTable
作为第二个键(例如,如果显式传递了CallerMemberName
参数,则这允许更多的备注,并且每个方法还可以选择更多的备注)。
第三个键是调用的参数。
因此,我们有3种类似于运行时字典的搜索,而不是1,但是语法更加简洁,IMO。
值得吗?我不知道,但我对优雅的渴望得到满足。
如果其他人有兴趣,我将其中的测试内容作为参考:
cacheKey