无论如何在C#中缓存函数/方法

时间:2011-02-08 04:28:08

标签: c# .net

我厌倦了一次又一次地将代码写入代码以缓存数据访问层中的对象。

无论如何都要缓存c#函数结果而不需要对函数进行太多更改。

目前是否有任何框架支持此功能?

我可以通过编写自定义“c#函数属性”来存档吗?如果是这样,请给我一些积分来开始实施?

8 个答案:

答案 0 :(得分:22)

可能性1:使用IL编织

之前提到过Postsharp。

您也可以尝试MethodCache.Fody包。

可能性2:使用代理/拦截框架

示例(Ninject& Ninject.Interception):

public class CacheAttribute : InterceptAttribute
{
    public override IInterceptor CreateInterceptor(IProxyRequest request)
    {
        return request.Context.Kernel.Get<CachingInterceptor>();
    }
}

public class CachingInterceptor : IInterceptor
{
    private ICache Cache { get; set; }

    public CachingInterceptor(ICache cache)
    {
        Cache = cache;
    }

    public void Intercept(IInvocation invocation)
    {
        string className = invocation.Request.Target.GetType().FullName;
        string methodName = invocation.Request.Method.Name;

        object[] arguments = invocation.Request.Arguments;

        StringBuilder builder = new StringBuilder(100);
        builder.Append(className);
        builder.Append(".");
        builder.Append(methodName);

        arguments.ToList().ForEach(x =>
        {
            builder.Append("_");
            builder.Append(x);
        });

        string cacheKey = builder.ToString();

        object retrieve = Cache.Retrieve<object>(cacheKey);

        if (retrieve == null)
        {
            invocation.Proceed();
            retrieve = invocation.ReturnValue;
            Cache.Store(cacheKey, retrieve);
        }
        else
        {
            invocation.ReturnValue = retrieve;
        }
    }
}

然后你可以装饰这样的函数:

[Cache]
public virtual Customer GetCustomerByID(int customerID)
{
    return CustomerRepository.GetCustomerByID(customerID);
}

截获的函数必须是虚函数,并且必须由Ninject内核创建类。如果您依赖性能,可以直接通过Castle.DynamicProxy代理类(Ninject.Extensions.Interception.DynamicProxy在内部使用它)。

可能性3:使用表达式包装器

您可以将函数作为表达式传递,生成包含类,方法和参数信息的缓存键,并在缓存中找不到表达式(如果未找到)。这比AOP / Proxy框架增加了更多的运行时开销,但对于简单的解决方案就足够了。

private T CacheAction<T>(Expression<Func<T>> action, [CallerMemberName] string memberName = "") where T : class
{
    MethodCallExpression body = (MethodCallExpression)action.Body;

    ICollection<object> parameters = new List<object>();

    foreach (MemberExpression expression in body.Arguments)
    {
        parameters.Add(((FieldInfo)expression.Member).GetValue(((ConstantExpression)expression.Expression).Value));
    }

    StringBuilder builder = new StringBuilder(100);
    builder.Append(GetType().FullName);
    builder.Append(".");
    builder.Append(memberName);

    parameters.ToList().ForEach(x =>
    {
        builder.Append("_");
        builder.Append(x);
    });

    string cacheKey = builder.ToString();

    T retrieve = Cache.Retrieve<T>(cacheKey);

    if (retrieve == null)
    {
        retrieve = action.Compile().Invoke();
        Cache.Store(cacheKey, retrieve);
    }

    return retrieve;
}

public Customer GetCustomerByID(int customerID)
{
    return CacheAction(() => CustomerRepository.GetCustomerByID(customerID));
}

答案 1 :(得分:6)

您可以使用PostSharp创建缓存属性。 Here就是一个例子。

答案 2 :(得分:4)

如果我认为你的问题是正确的,你想要的正确术语是memoization。维基百科提供了有关此主题的更多细节。不幸的是,没有提到支持它的C#库。

答案 3 :(得分:2)

Cache Application block是微软对.NET中缓存的内置库的回答。

答案 4 :(得分:2)

第一次运行后,

Lazy存储它的值。 示例:http://msdn.microsoft.com/en-us/vstudio/bb870976

答案 5 :(得分:0)

我建议使用Spring.Net AOP。 它基本上创建了一个代理,并且可以从/向缓存重定向调用。 http://www.springframework.net/doc/reference/html/aop-quickstart.html

然后你可以得到类似的建议:

public class CachingAroundAdvice : IMethodInterceptor
{
    #region Variable Declarations
    private Priority priority = Priority.Normal;
    #endregion

    public object Invoke(IMethodInvocation invocation)
    {
        // declare local variables
        string cacheKey = string.Empty;
        object dataObject = null;

        // build cache key with some algorithm
        cacheKey = CreateCacheKey(invocation.Method, invocation.Arguments);

        // retrieve item from cache
        dataObject = CacheManager.Cache.GetData(cacheKey);

        // if the dataobject is not in cache proceed to retrieve it
        if (null == dataObject)
        {
            dataObject = invocation.Proceed();

            // add item to cache
            CacheManager.Cache.Add(cacheKey, dataObject, CachePriority, null, Expiration);
        }

        // return data object
        return dataObject;
    }

答案 6 :(得分:0)

我使用System.Runetime.Caching命名空间的简单实现:

public class InMemoryCache : ICacheService
{
    public T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class
    {
        T item = MemoryCache.Default.Get(cacheKey) as T;
        if (item == null)
        {
            item = getItemCallback();
            MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddHours(4));
        }
        return item;
    }

    public void Clear(string cacheKey)
    {
        MemoryCache.Default.Remove(cacheKey);
    }
}

interface ICacheService
{
    T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class;
    void Clear(string cacheKey);
}

可以按以下方式使用:

var cacheProvider = new InMemoryCache();
var cachedResult = cacheProvider.GetOrSet("YourCacheKey",
                () => MethodToCache());

第一次调用该方法将缓存结果,下一次调用将返回缓存的结果。

答案 7 :(得分:0)

您可以使用Dictionary来缓存该函数。字典将键映射到值,而函数将参数映射到值。因此,从概念上讲,字典适合作为函数的缓存。这是一个简单的类:

/// <summary>
/// The lazy function map caches the results of calls to the backing function. Every time the function is called on an argument u and returns v,
/// the pair (u, v) is stored in the dictionary.
/// </summary>
class LazyFunctionMapImpl<T, U> : ILazyFunctionMap<T, U>
{
    private readonly Dictionary<T, U> _backingDictionary;
    private readonly Func<T, U> _backingFunction;

    public LazyFunctionMapImpl(Func<T, U> backingFunction)
    {
        _backingDictionary = new Dictionary<T, U>();
        _backingFunction = backingFunction;
    }

    public U this[T index]
    {
        get
        {
            if (_backingDictionary.ContainsKey(index))
            {                    
                return _backingDictionary[index];
            }                
            U valueAtIndex = _backingFunction(index);
            _backingDictionary.Add(index, valueAtIndex);
            return valueAtIndex;
        }
    }

    public void Clear()
    {
        _backingDictionary.Clear();
    }
}

下面是几个接口:

/// <summary>
/// A function map that should lazily cache param/result pairs until clear is called.
/// </summary>    
public interface ILazyFunctionMap<T, U> : IFunctionMap<T, U>
{
    /// <summary>
    /// Should invalidate any caches forcing the underyling function to be called afresh
    /// </summary>
    void Clear();
}

public interface IFunctionMap<T, U>
{
    /// <summary>
    /// Mapped values representing the underlying function.
    /// </summary>
    U this[T index] { get; }
}