Is there anyway to cache function/method in C#

Possibility 1: Use IL Weaving

Postsharp was mentioned before.

You could also try the MethodCache.Fody package.

Possibility 2: Use an Proxy / Interception Framework

Example (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;
        }
    }
}

Then you could decorate functions like this:

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

Intercepted functions have to be virtual and classes must be created by the Ninject kernel. If you rely on performance, you could proxy classes directly via Castle.DynamicProxy (which is internally used by Ninject.Extensions.Interception.DynamicProxy).

Possibility 3: Use an Expression wrapper

You could pass the function as expression, generate a caching key containing class, method and parameter information and invoke the expression if not found in your Cache. This adds more runtime overhead than AOP / Proxy frameworks, but will be sufficient for simple solutions.

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));
}

You can create caching attributes with PostSharp. Here is an example.


If I read you question correct, the right term for what you want is memoization. Wikipedia gives more details on this subjects. Unfortunately there is no reference to a C# library supporting it.

Tags:

C#

.Net