-
Notifications
You must be signed in to change notification settings - Fork 112
Aspects.Cache
Igor Tkachev edited this page May 20, 2016
·
1 revision
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;
}
}