在我为优雅而奋斗的过程中,我终于找到了我认为最好的语法:
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
使用,作为第二个键(例如,这允许更多的内存化,如果传递
cacheKey
因此,我们有3个类似于运行时字典的搜索,而不是1个,但是语法要干净得多,IMO。
值得吗?我不知道,但我对优雅的渴望已经满足了。
[TestFixture]
public class MemoizerTest
{
[Test]
public void MemoizationWorksOnFuncs()
{
int counter = 0;
Func<int, int> f = x => counter += x;
Assert.That(this.Memoized(1, f), Is.EqualTo(1));
Assert.That(this.Memoized(2, f), Is.EqualTo(3));
Assert.That(this.Memoized(2, f), Is.EqualTo(3));
Assert.That(this.Memoized(1, f), Is.EqualTo(1));
}
private class MemoizedTest
{
private int _counter = 0;
public int Method(int p)
=> this.Memoized(p, x => { return _counter += x; });
}
[Test]
public void MemoizationWorksOnInstances()
{
var obj1 = new MemoizedTest();
Assert.That(obj1.Method(5), Is.EqualTo(5));
Assert.That(obj1.Method(4), Is.EqualTo(9));
Assert.That(obj1.Method(5), Is.EqualTo(5));
Assert.That(obj1.Method(1), Is.EqualTo(10));
Assert.That(obj1.Method(4), Is.EqualTo(9));
obj1 = new MemoizedTest();
Assert.That(obj1.Method(5), Is.EqualTo(5));
Assert.That(obj1.Method(4), Is.EqualTo(9));
Assert.That(obj1.Method(5), Is.EqualTo(5));
Assert.That(obj1.Method(1), Is.EqualTo(10));
Assert.That(obj1.Method(4), Is.EqualTo(9));
}
[Test]
[Ignore("This test passes only when compiled in Release mode")]
public void WeakMemoizationCacheIsCleared()
{
var obj1 = new MemoizedTest();
var r1 = obj1.Method(5);
MemoizerExtension._weakCache.TryGetValue(obj1, out var cache);
var weakRefToCache = new WeakReference(cache);
cache = null;
GC.Collect(2);
obj1 = null;
GC.Collect();
GC.Collect();
var msg = weakRefToCache.TrackResurrection;
Assert.That(weakRefToCache.IsAlive, Is.False, "The weak reference should be dead.");
Assert.That(r1, Is.EqualTo(5));
}
}