Note: This post is part of a series and you can find the rest of the parts in the series index.
.NET has had one out of the box way to do caching in the past, System.Web.Caching. While a good system it suffered from two issues. Firstly it was not extensible, so if you wanted to cache to disk or SQL or anywhere other than memory you were out of luck and secondly it was part of ASP.NET and while you could use it in WinForms it took a bit of juggling.
The patterns & practises team saw these issues and have provided a caching application block in their Enterprise Library which has been used by everyone who did not want to re-invent the wheel. Thankfully from .NET 4 there is a caching system now included in the framework which solves those two issues above. This is known as System.Runtime.Caching.
Slow Example
To see how to use it lets start with a process which we can cache. I have a class called Demo which has a property named Times which is of type IEnumerable<DateTime>. To set the value of Times, you call the SetTimes method and that populates the property with 5 values. However there is a delay of 500ms between each adding of DateTime to the Times property, so it takes 2.5secs to run. In my Program class I have a method, PrintTimes which creates a new Demo object, calls SetTimes and then prints the value to screen. Lastly I in my Main method I call PrintTimes three times – in total it takes 7.5secs to run.
class Program { public static void Main() { PrintTimes(); PrintTimes(); PrintTimes(); } private static void PrintTimes() { Demo demo = new Demo(); Stopwatch stopwatch = Stopwatch.StartNew(); demo.SetTimes(); foreach (DateTime time in demo.Times) { Console.WriteLine(time); } stopwatch.Stop(); Console.WriteLine("It took {0} to print out the times", stopwatch.ElapsedMilliseconds); } } class Demo { public List<DateTime> Times { get; set; } public void SetTimes() { if (Times == null) { Times = new List<DateTime>(); for (int counter = 0; counter < 5; counter++) { Thread.Sleep(500); Times.Add(DateTime.Now); } } } }
The output is from this example code is below. Note the times printed are constantly changing and that it takes ~2500ms to print out each set of values.
Cache Example
Now I change the PrintTimes method to incorporate the caching by creating an ObjectCache which I set to use the default MemoryCache instance. I can check if the cache contains an object using the .Contains method, I retrieve from the cache using the .Get method and I add to the cache using the .Add method:
private static void PrintTimes() { Demo demo; Stopwatch stopwatch = Stopwatch.StartNew(); ObjectCache cache = MemoryCache.Default; if (cache.Contains("demo")) { demo = (Demo)cache.Get("demo"); } else { demo = new Demo(); demo.SetTimes(); cache.Add("demo", demo, new CacheItemPolicy()); } foreach (DateTime time in demo.Times) { Console.WriteLine(time); } stopwatch.Stop(); Console.WriteLine("It took {0} to print out the times", stopwatch.ElapsedMilliseconds); }
This gives the output:
Note the time to print out for the first one is about 60ms longer but the second two is down to 0ms and also note how the values in the three sets in are the same.
CacheItemPolicy
In the above example I just use a default CacheItemPolicy, but you could do a lot more with the CacheItemPolicy:
- AbsoluteExpiration: Set a date/time when to remove the item from the cache.
- ChangeMonitors: Allows the cache to become invalid when a file or database change occurs.
- Priority: Allows you to state that the item should never be removed.
- SlidingExpiration: Allows you to set a relative time to remove the item from cache.
- UpdateCallback & RemovedCallback: Two events to get notification when an item is removed from cache. UpdateCallback is called before an item is removed and RemovedCallBack is called after an item is removed.
Things to watch out for
- .NET 4 only ships with support for memory caching out of the box, so you will need to build your own provider if you want any other cache source.
- You must add the System.Runtime.Caching.dll reference to get access to the namespace and classes.
- This is NOT part of the .NET 4 client profile, it is only in the full .NET 4 Framework. I have logged an issue with Connect to move the assembly into the client profile and if you agree with this idea, please vote on it: 558309