Skip to content
Igor Tkachev edited this page May 20, 2016 · 1 revision

Home / Aspects

This aspect helps to cache method calls. The aspect uses input method parameters to create a cache key and caches return value and all output (both ref and out) parameters. By default only value types and string type of the method parameters are used to create a cache key. Any other types are ignored. This behavior can be changed by assigning the CacheAspect.IsCacheableParameterType property to a delegate providing custom logic.

CacheAspect.cs

using System;
using System.Reflection;

using NUnit.Framework;

using BLToolkit.Aspects;
using BLToolkit.Reflection;

namespace HowTo.Aspects
{
    public abstract class TestClass
    {
        public static int Value;

        // This is a method we will cache. Cached return value depends on input parameters.
        // We will change the 'Value' field outside of the class and see how it affects the result.
        //
        [Cache(MaxCacheTime=500, IsWeak=false)]
        public virtual int CachedMethod(int p1, int p2)
        {
            return Value;
        }

        public static TestClass CreateInstance()
        {
            // Use TypeAccessor to create an instance of an abstract class.
            //
            return TypeAccessor<TestClass>.CreateInstance();
        }
    }

    [TestFixture]
    public class CacheAspectTest
    {
        [Test]
        public void Test1()
        {
            TestClass tc = TestClass.CreateInstance();

            DateTime begin = DateTime.Now;

            // Initial setup for the test static variable.
            //
            TestClass.Value = 777;

            while (tc.CachedMethod(2, 2) == 777)
            {
                // This change will not affect the Test method return value for 500 ms.
                //
                TestClass.Value++;
            }

            double totalMilliseconds = (DateTime.Now - begin).TotalMilliseconds;

            Assert.GreaterOrEqual(totalMilliseconds, 500);
        }

        [Test]
        public void Test2()
        {
            TestClass tc = TestClass.CreateInstance();

            // Return value depends on parameter values.
            //
            TestClass.Value = 1; Assert.AreEqual(1, tc.CachedMethod(1, 1));
            TestClass.Value = 2; Assert.AreEqual(1, tc.CachedMethod(1, 1)); // no change
            TestClass.Value = 3; Assert.AreEqual(3, tc.CachedMethod(2, 1));

            // However we can clear cache manually.
            // For particular method:
            //
            CacheAspect.ClearCache(typeof(TestClass), "CachedMethod", typeof(int), typeof(int));
            TestClass.Value = 4; Assert.AreEqual(4, tc.CachedMethod(2, 1));

            // By MethodInfo:
            //
            MethodInfo methodInfo = tc.GetType().GetMethod("CachedMethod", new Type[] { typeof(int), typeof(int) });
            CacheAspect.ClearCache(methodInfo);
            TestClass.Value = 5; Assert.AreEqual(5, tc.CachedMethod(2, 1));

            // For the all cached methods.
            //
            CacheAspect.ClearCache();
            TestClass.Value = 6; Assert.AreEqual(6, tc.CachedMethod(2, 1));
        }
    }
}

If we decompile the actual emitted TestClass class, we may see something like the following:

[BLToolkitGenerated]
public sealed class TestClass : HowTo.Aspects.TestClass
{
    private static CallMethodInfo _methodInfo;
    private static IInterceptor   _interceptor;

    public override int CachedMethod(int p1, int p2)
    {
        int returnValue = 0;

        if (_methodInfo == null)
        {
            _methodInfo = new CallMethodInfo((MethodInfo)MethodBase.GetCurrentMethod());
        }

        InterceptCallInfo info = new InterceptCallInfo();

        info.Object             = this;
        info.CallMethodInfo     = _methodInfo;
        info.ParameterValues[0] = p1;
        info.ParameterValues[1] = p2;
        info.ReturnValue        = returnValue;
        info.InterceptResult    = InterceptResult.Continue;
        info.InterceptType      = InterceptType.BeforeCall;

        if (_interceptor == null)
        {
            _interceptor = new CacheAspect();
            _interceptor.Init(_methodInfo, "MaxCacheTime=500;IsWeak=False");
        }

        // 'BeforeCall' step checks if the method is cached.
        // If it is and the cache is not expired, the Intercept method populates 
        // return value and output parameters with the cached values and 
        // sets info.InterceptResult to InterceptResult.Return.
        // See the CacheAspect.BeforeCall method for details.
        //
        _interceptor.Intercept(info);

        returnValue = (int)info.ReturnValue;

        if (info.InterceptResult != InterceptResult.Return)
        {
            // If the method call is not cached, target method is called.
            //
            returnValue = base.CachedMethod(p1, p2);

            info.ReturnValue     = returnValue;
            info.InterceptResult = InterceptResult.Continue;
            info.InterceptType   = InterceptType.AfterCall;

            // 'AfterCall' step stores parameters and return values in the cache.
            // See the CacheAspect.AfterCall method for details.
            //
            _interceptor.Intercept(info);

            returnValue = (int)info.ReturnValue;
        }

        return returnValue;
    }
}
Clone this wiki locally