Feedbacks, comments and suggestions are greatly appreciated.
Introduction
Caching based on virtual methods offers caching for methods without the need to code and prepare them for caching.
Background
The idea behind this is to enable caching for any class in project and even DLL references if methods are virtual with minimal preconfiguration (keeping classes clean, not worrying too much about caching).
You can have multiple instances of cache, every instance has its own cached values and configuration and they are sharing information about types for performance and memory, it's also thread safe.
How It Works?
You start by requesting the instance of an object for which you want caching enabled for a required type.
var test = MainCache.I<Test>();
Cache creates a derived type (only first time, every other time returns just new instance) that inherits Test
class and overloads all virtual
methods that are not excluded by configuration. This is accomplished by MSIL, similar to how entity framework inherits entities for property lazy loading and more.
Then when you call...
var i = test.Mtd(1);
...your method is not called directly but the one that overrides it on derived class and from that overridden method on Cache
object is called that creates a cache key based on method "id
" and hash created from parameter values, then it decides whether the original method should be called or cached value is returned if it exists for the generated key.
Inside the Cache Class
After first call of...
MainCache.I<Test>();
...method, it gets to...
public dynamic I(Type type)
{
DerivedTypeCache derivedTypeCache = getDerivedType(type);
return derivedTypeCache.GetInstance(this);
}
...where it first gets or creates derived type, after that it gets instance of that derived type and sets the instance of cache on it for later usage.
Derived types are shared among all cache instances, also values that have set expiration are shared among instances.
Values that have set expiration are stored in...
private static ReaderWriterLockWrapper<Dictionary<int,
Dictionary<Cache, List<CacheInfo>>>> valuesExpiration;
...int
key in Dictionary
represents a second in time diminished by initial second that is set on static
constructor of Cache
and values are stored under second that they expire on. That's why seconds doesn't need to be type long
.
With that, there is a single timer for all cache instances that fires every second and checks dictionary for expired values, single timer for application. It stores what second was last checked and starts checking from it to current one.
Usage Example
using Syrilium.Caching;
using System;
using System.Threading;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public static Cache MainCache = new Cache();
public MainWindow()
{
InitializeComponent();
var test = MainCache.I<Test>();
int io;
var i = test.Mtd(1, out io);
int io2;
var i2 = test.Mtd(1, out io2);
var i3 = test.Mtd(1);
var i4 = test.Mtd(1);
var i5 = test.Mtd(2);
var i6 = test.Mtd(2);
var test2 = MainCache.I<Test>();
int io3;
var i7 = test2.Mtd(1, out io3);
var i8 = test2.Mtd(1);
var i9 = test2.Mtd(2);
MainCache.Configure.Type<object>().Exclude().AffectsDerivedTypes();
MainCache.Configure.Method<Test>
(t => t.Mtd(0, out io3)).Method(t => t.Mtd(0));
MainCache.Configure.Type<Test>()
.Method(t => t.Mtd(0, out io3))
.Method(t => t.Mtd(0)).IdleReadClearTime(TimeSpan.FromSeconds(3));
var testNoCache = MainCache.I<TestNoCache>();
var i10 = testNoCache.Mtd(1);
var i11 = testNoCache.Mtd(1);
var test3 = MainCache.I<Test>();
var i12 = test3.Mtd(1);
var i13 = test3.Mtd(1);
Thread.Sleep(5000);
var i14 = test3.Mtd(1);
var i15 = test3.Mtd(1);
}
}
public class Test
{
public static int Inc;
public virtual int Mtd(int i)
{
return ++Inc;
}
public virtual int Mtd(int i, out int io)
{
return io = ++Inc;
}
}
public class TestNoCache
{
public static int Inc;
public virtual int Mtd(int i)
{
return ++Inc;
}
public virtual int Mtd(int i, out int io)
{
return io = ++Inc;
}
}
}