哇——真是巧合——我最近刚刚发布了一个关于opaque keys in C# 的问题……因为我正在尝试实现一些与函数结果缓存相关的东西。好搞笑。
这种类型的元编程在 C# 中可能会很棘手……尤其是因为泛型类型参数会导致尴尬的代码重复。你经常会在多个地方重复几乎相同的代码,使用不同的类型参数,以实现类型安全。
所以这是我对您的方法的变体,它使用我的不透明键模式和闭包来创建可缓存的函数。下面的示例演示了带有一个或两个参数的模式,但它相对容易扩展到更多。它还使用扩展方法创建一个透明模式,使用 AsCacheable() 方法用可缓存的 Func 包装 Func。闭包捕获与函数关联的缓存 - 并使其对其他调用者透明。
这种技术与您的方法有许多相同的限制(线程安全、保持引用等)——我怀疑它们并不难克服——但它确实支持一种简单的方法来扩展到多个参数,并且它允许缓存函数完全可以替换为常规函数——因为它们只是一个包装委托。
还值得注意的是,如果您创建 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);
}
}