Source code for this article is available here. All usual expressions of warranties apply, i.e. it’s your fault if the code you download decides it wants to simulate WWIII or take control of space ships. Entirely your fault.

Let me begin with saying THANK YOU for getting caching out of System.Web and creating a platform agnostic caching runtime. Most of us have probably been using Enterprise Library or similar to deliver “non-webby” (it’s a technical term) caching capabilities and by and large, it’s served us well over the years. That being said, the new runtime is more or less officially the way to go but there’s still a little tweaking required, in my opinion, to get it on par with EntLib.

Enterprise Library’s caching block is a bit more intuitive for those creating customizations. For example, the cache manager in EntLib is responsible for polling the cached item to see if it’s expired rather than the cached item needing to inform the cache runtime that a change has occurred. I consider the latter acceptable if we’re dealing with more or less built-in notification structures such as SqlDependency or file change notifications, but what about when you simply want to test against arbitrary information when the item is accessed? At that point you need to start looking at timers to do the polling which will become undesireable quite quickly if you don’t constrain it.  With the polling approach, you at least have the option to begin utilizing multiple threads if necessary rather than being locked into it. Is it a problem on a simple desktop application? Probably not. Transfer that to the server space and then you might have to strongly consider what you cache using this approach if you’re dealing with a high volume application – not that that’s a bad thing.

That minor detour aside, a specific expiration policy (not to be confused with the .NET 4 caching CacheItemPolicy) is missing from .NET 4 that I made fairly extensive use of through EntLib – the ExtendedTimeFormat expiration. This expiration policy allows you to state that you want to evict an item from the cache every Nth minute of every Nth hour of every Nth day of every Nth month…you get the picture. This policy is great for implementing recurrence patterns – say you want a task to run every Nth hour – use the ExtendedTimeFormat expiration policy. The closest you really can get in .NET 4 is using the CacheItemPolicy’s AbsoluteTime property, but that simply lets you specify how long an item should be in the cache before it’s evicted. On top of that you’d need to write additional code to translate your recurrence pattern to what the next AbsoluteTime is. The ExtendedTimeFormat wraps this up for you in a convenient package.

I’m not going to walk through anything other than the custom ChangeMonitor implemented to support this, but you can download the entire source at the end of this post. I should say that I did tweak the behavior of the original ExtendedTimeFormat implementation to support resolution down to the second. This requires a shorter timer interval to support which isn’t necessarily the best thing, but it does provide a little extra flexibility. Tweak the timer code at your own discretion.
Let’s begin by creating a new command line project called CustomCacheExpiration and add a new class called ExtendedTimeFormatChangeMonitor (I changed the name around as the original bugged me from an aesthetic perspective) and we’ll assume there’s a class present to parse an extended time format string (see source code!).  While you’re at it, add a reference to System.Runtime.Caching. If you have the EntLib source code, you can go ahead and include the ExtendedFormat class – just make sure to update the namespace to coincide with your project. At this stage, we should look something like the the following diagram.

Now, let’s make the ExtendedTimeFormatChangeMonitor class derive from SystemRuntime.Caching.ChangeMonitor. There are a couple of things that we need to do in the constructor – specifically we need to initialize the UniqueId property and call ChangeMonitor.InitializationComplete once we’re ready to exit the constructor (in a non-exception case). Aside from that, everything else is left to our discretion. In our case, let’s initialize the ExtendedTimeFormat class with the passed in string and set up a timer to poll for changes in the time that result in the expiration of the item.

         public ExtendedTimeFormatChangeMonitor(string timeFormat)
             bool initializationComplete = false ;
             // check arguments
             if (string .IsNullOrEmpty(timeFormat))
                 throw new ArgumentException ("The time format cannot be null." , "timeFormat" );
                 _format = new ExtendedFormat (timeFormat);
                 // Get the modified extended format
                 this._extendedFormat = timeFormat;
                 // Convert to UTC in order to compensate for time zones 
                 this._lastUsedTime = DateTime.Now.ToUniversalTime();
                 this._uniqueId = Guid.NewGuid().ToString();
                 _timer = new System.Timers.Timer ();
                 _timer.Interval = 1000;
                 _timer.Elapsed += new System.Timers.ElapsedEventHandler (CheckForChanges);
                 _timer.Enabled = true ;
                 initializationComplete = true ;
                 if (!initializationComplete)
                     Dispose(true );

Now that the constructor is more or less wrapped up, let’s attach something to the Elapsed event of the timer that will actually execute the check to see if the item is still valid or not. If the item is valid, we won’t do anything, but if it is, we’ll call ChangeMonitor.OnChange to inform the runtime that the item has changed. This will result in it being evicted from the cache.

         /// <summary>
         /// Specifies if item has expired or not.
         /// </summary>
         /// <returns>
         /// Returns true if the data is expired otherwise false
         /// </returns>
         public void CheckForChanges(object sender, System.Timers.ElapsedEventArgs args)
             // Convert to UTC in order to compensate for time zones 
             DateTime nowDateTime = DateTime.Now.ToUniversalTime();
             // Check expiration
             if (_format.IsExpired(_lastUsedTime, nowDateTime))
                 base.OnChanged(null );

Now that our custom ChangeMonitor is wrapped up, let’s write some code to test it out. In this example we’re going to add an item to the cache that’s set to expire every second of every minute of every hour of every…yadda yadda yadda. Let’s also add a delegate to the policy so that we can write out a message that shows when the item was evicted. Once you have some code approximating the sample below, hit F5 and let’s see it run. You should hopefully see a message that shows when the item was evicted from the cache. If not…wait a while or double-check your copy and paste 😉

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Runtime.Caching;
 namespace CustomCacheExpiration
     class Program
         static void Main(string [] args)
             MemoryCache cache;
             cache = new MemoryCache ("mycache" );
             CacheItemPolicy policy;
             policy = new CacheItemPolicy ();
             policy.ChangeMonitors.Add(new ExtendedTimeFormatChangeMonitor ("* * * * * *" ));
             policy.RemovedCallback = new CacheEntryRemovedCallback (ItemRemoved);
             Console .WriteLine( "Added item at {0}." , DateTime.Now );
             cache.Add("mykey" , "myvalue" , policy);
             Console.WriteLine(cache.Get("mykey" ));
         static void ItemRemoved(CacheEntryRemovedArguments args )
             Console.WriteLine( "Item removed at {0}." , DateTime.Now ); 

Now that you’ve seen this, hopefully it reinforces why I think that the EntLib approach is better. EntLib, in my opinion, supports the native notification model (e.g. files, SQL, etc…) as well as the polling model while minimizing the resources being used. On top of that you have a handy and cheap way to implement recurrence patterns in your application. For an additional excercise, you may want to consider creating a custom CacheItemPolicy that can automatically re-add the item back to the cache with the same expiration.


About The Author

Leave a Reply

Your email address will not be published.