为了好玩,我正在玩一个类来轻松缓存功能结果。基本的想法是你可以使用你想要的任何功能 - 虽然你只想将它用于相对昂贵的功能 - 并且可以轻松地将它包装起来使用相对便宜的字典查找,以便以后使用相同的参数运行。真的没什么可说的:
public class AutoCache<TKey, TValue>
{
public AutoCache(Func<TKey, TValue> FunctionToCache)
{
_StoredFunction = FunctionToCache;
_CachedData = new Dictionary<TKey, TValue>();
}
public TValue GetResult(TKey Key)
{
if (!_CachedData.ContainsKey(Key))
_CachedData.Add(Key, _StoredFunction(Key));
return _CachedData[Key];
}
public void InvalidateKey(TKey Key)
{
_CachedData.Remove(Key);
}
public void InvalidateAll()
{
_CachedData.Clear();
}
private Dictionary<TKey, TValue> _CachedData;
private Func<TKey, TValue> _StoredFunction;
}
不幸的是,还有一些额外的限制使得它没有那么有用。我们还可以添加一些功能以及实现的其他注意事项。我正在寻找关于如何改进以下任何一点的想法:
作为一个参考点,如果我在实际代码中使用它,我认为最可能的地方是作为业务逻辑层的一部分,我使用此代码将数据访问层中的方法包装起来来自查找表的数据。在这种情况下,相对于字典,数据库之旅将是昂贵的,并且几乎总是只有一个“键”值用于查找,因此它是一个很好的匹配。
答案 0 :(得分:8)
这种自动缓存功能结果的另一个名称是memoization。对于公共接口,请考虑以下几点:
public Func<T,TResult> Memoize<T,TResult>(Func<T,TResult> f)
...只需使用多态就可以将T存储在对象字典中。
扩展委托范围可以通过currying和部分功能应用来实现。像这样:
static Func<T1,Func<T2,TResult>> Curry(Func<T1,T2,TResult> f)
{
return x => y => f(x, y);
}
// more versions of Curry
由于Curry
将多个参数的函数转换为单个参数的函数(但可能返回函数),因此返回值本身有资格进行memoization。
另一种方法是使用反射来检查委托类型,并将元组存储在字典中,而不仅仅是参数类型。一个简单的元组只是一个数组包装器,其哈希码和相等逻辑使用深度比较和散列。
弱引用可以帮助失效,但使用WeakReference
键创建字典很棘手 - 最好在运行时的支持下完成(WeakReference值更容易)。我相信那里有一些实现。
通过在内部字典上锁定突变事件可以轻松完成线程安全,但拥有无锁字典可以提高重度并发场景的性能。那个字典可能更难创造 - 尽管有一个有趣的presentation on one for Java here。
答案 1 :(得分:2)
这种类型的元编程对于C#来说可能很棘手......特别是因为泛型类型参数会导致代码重复笨拙。您经常最终会在多个地方重复使用不同类型参数的相同代码,以实现类型安全。
所以这是我的方法的变体,它使用我的opaque键模式和闭包来创建可缓存的函数。下面的示例演示了带有一个或两个参数的模式,但它相对容易扩展到更多。它还使用扩展方法来创建用于包装Func&lt;&gt;的透明图案。使用可缓存的Func&lt;&gt;使用AsCacheable()
方法。闭包捕获与该函数关联的缓存 - 并使其对其他调用者保持透明。
这种技术有许多与你的方法相同的限制(线程安全,保留引用等) - 我怀疑它们不是很难克服 - 但它支持一种简单的方法来扩展到多个参数,它允许可缓存的函数完全可以用常规函数替换 - 因为它们只是一个包装器委托。
值得注意的是,如果您创建CacheableFunction的第二个实例 - 您将获得一个单独的缓存。这既可以是力量也可以是弱点......因为在某些情况下你可能没有意识到这种情况正在发生。
以下是代码:
public interface IFunctionCache
{
void InvalidateAll();
// we could add more overloads here...
}
public static class Function
{
public class OpaqueKey<A, B>
{
private readonly object m_Key;
public A First { get; private set; }
public B Second { get; private set; }
public OpaqueKey(A k1, B k2)
{
m_Key = new { K1 = k1, K2 = k2 };
First = k1;
Second = k2;
}
public override bool Equals(object obj)
{
var otherKey = obj as OpaqueKey<A, B>;
return otherKey == null ? false : m_Key.Equals(otherKey.m_Key);
}
public override int GetHashCode()
{
return m_Key.GetHashCode();
}
}
private class AutoCache<TArgs,TR> : IFunctionCache
{
private readonly Dictionary<TArgs,TR> m_CachedResults
= new Dictionary<TArgs, TR>();
public bool IsCached( TArgs arg1 )
{
return m_CachedResults.ContainsKey( arg1 );
}
public TR AddCachedValue( TArgs arg1, TR value )
{
m_CachedResults.Add( arg1, value );
return value;
}
public TR GetCachedValue( TArgs arg1 )
{
return m_CachedResults[arg1];
}
public void InvalidateAll()
{
m_CachedResults.Clear();
}
}
public static Func<A,TR> AsCacheable<A,TR>( this Func<A,TR> function )
{
IFunctionCache ignored;
return AsCacheable( function, out ignored );
}
public static Func<A, TR> AsCacheable<A, TR>( this Func<A, TR> function, out IFunctionCache cache)
{
var autocache = new AutoCache<A,TR>();
cache = autocache;
return (a => autocache.IsCached(a) ?
autocache.GetCachedValue(a) :
autocache.AddCachedValue(a, function(a)));
}
public static Func<A,B,TR> AsCacheable<A,B,TR>( this Func<A,B,TR> function )
{
IFunctionCache ignored;
return AsCacheable(function, out ignored);
}
public static Func<A,B,TR> AsCacheable<A,B,TR>( this Func<A,B,TR> function, out IFunctionCache cache )
{
var autocache = new AutoCache<OpaqueKey<A, B>, TR>();
cache = autocache;
return ( a, b ) =>
{
var key = new OpaqueKey<A, B>( a, b );
return autocache.IsCached(key)
? autocache.GetCachedValue(key)
: autocache.AddCachedValue(key, function(a, b));
};
}
}
public class CacheableFunctionTests
{
public static void Main( string[] args )
{
Func<string, string> Reversal = s => new string( s.Reverse().ToArray() );
var CacheableReverse = Reversal.AsCacheable();
var reverse1 = CacheableReverse("Hello");
var reverse2 = CacheableReverse("Hello"); // step through to prove it uses caching
Func<int, int, double> Average = (a,b) => (a + b)/2.0;
var CacheableAverage = Average.AsCacheable();
var average1 = CacheableAverage(2, 4);
var average2 = CacheableAverage(2, 4);
}
}
答案 2 :(得分:0)
因为这主要是为了教育价值 - 你应该看看WeakReference类,它允许GC在多线程环境中清除你的类中未使用的句柄。这是.NET中非常常见的缓存模式
那说 - 告诫Emptor!每个缓存都不同。通过构建一个包罗万象的解决方案,你经常会遇到一个病态的情况,你的“缓存”只是一个美化的字典,有许多复杂的辅助方法,使你的代码很难。
答案 3 :(得分:0)
我正在使用这个简单的扩展,在这种情况下使用MemoryCache:
public static class FuncHelpers
{
/// <summary>
/// Returns a same function wrapped into cache-mechanism
/// </summary>
public static Func<TIn, TRes> Cached<TIn, TRes>(this Func<TIn, TRes> func,
Func<TIn,string> keySelector,
Func<TIn,CacheItemPolicy> policy)
{
var cache = new MemoryCache(Guid.NewGuid().ToString());
Func<TIn, TRes> f = (item) =>
{
var key = keySelector(item);
var newItem = new Lazy<TRes>(() => func(item));
var oldItem = cache.AddOrGetExisting(key,newItem , policy(item)) as Lazy<TRes>;
try
{
return (oldItem ?? newItem).Value;
}
catch
{
// Handle cached lazy exception by evicting from cache.
cache.Remove(key);
throw;
}
};
return f;
}
//simplified version
public static Func<TIn, TRes> Cached<TIn, TRes>(this Func<TIn, TRes> func, Func<TIn, string> keySelector,
TimeSpan duration)
{
if (duration.Ticks<=0) return func;
return Cached(func, keySelector,
item => new CacheItemPolicy() {AbsoluteExpiration = DateTimeOffset.Now + duration});
}
}
示例/用法:(缓存持续时间为42秒):
public class CachedCalculator
{
private Func<int, int> _heavyExpensiveMultiplier;
public Calculator(Func<int,int> heavyExpensiveMultiplier )
{
//wrap function into cached one
this._heavyExpensiveMultiplier
= heavyExpensiveMultiplier.Cached(x =>/*key for cache*/ x.ToString(), TimeSpan.FromSeconds(42));
}
//this uses cached algorithm
public int Compute(int x)
{
return _heavyExpensiveMultiplier(x);
}
}