Click here to Skip to main content
15,900,973 members
Articles / Programming Languages / C#
Article

Generic Lazy Load Cache Class

Rate me:
Please Sign up or sign in to vote.
3.25/5 (4 votes)
5 Sep 2008CPOL2 min read 52.1K   24   25
Presents a base class for creating a singleton lazy load cache.

Introduction

This generic class can be inherited and used as a simple memory cache for lazy loading objects.

Background

In my applications, I noticed I was often implementing a lazy load cache. I use it for static or near-static data that I am loading from a database and which I don't want to have in an object graph multiple times.

The requirements for the class are:

  • Loading of objects only once. After the initial load, requests for the object would return the copy already in memory (i.e. Lazy Load).
  • A Singleton pattern for the cache so that there is only one instance ever.
  • Retrieval of objects via a key object (dictionary).
  • Derived classes are responsible for the initial load of an object.

Having done this a few times, it seemed to me that I should be able to use Generics to create a reusable base class for my cache. I wanted to use the Template pattern so that each derived class implements its own loading mechanism, and combine this with the Singleton pattern.

Using the Code

To use the code, you simply inherit from the base class. The derived class needs a private/protected constructor to make sure it cannot be instantiated directly, and needs to override the GetItem member to actually obtain the object from somewhere when it is not in the cache.

There are three types that must be supplied:

  1. T: The type of the class which is inheriting the LazyLoadCacheBase class.
  2. TKey: The type of the key.
  3. TValue: The type of the objects stored in the cache.

Here's the class:

C#
/// <summary>
/// This base class can be inherited to implement a lazy load cache
/// </summary>
/// <typeparam name="T">Pass in the type of the derived class (the type of the class that 
/// inherits this base class)</typeparam>
/// <typeparam name="TKey">Type of the dictionary key objects</typeparam>
/// <typeparam name="TValue">Type of the dictionary value objects</typeparam>
internal abstract class LazyLoadCacheBase<T, TKey,
    TValue> where T: LazyLoadCacheBase<T, TKey, TValue>
{
    private Dictionary<TKey, TValue> _dictionary = new Dictionary<TKey, TValue>();

    abstract protected TValue GetItem(TKey key);

    protected LazyLoadCacheBase()
    {
    }

    public static T Instance
    {
        get
        {
            return Creator.Singleton;
        }
    }

    public Dictionary<TKey, TValue> Dictionary
    {
        get { return _dictionary; }
        set {_dictionary = value;}
    }

    public TValue GetValue(TKey key)
    {
        if (!_dictionary.ContainsKey(key))
        {
            TValue item = GetItem(key);
            _dictionary.Add(key, item);
        }
        return _dictionary[key];
    }

    private sealed class Creator
    {
        private static readonly T _instance = (T)typeof(T).InvokeMember(typeof(T).Name,
            BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.Instance,
            null, null, null);

        internal static T Singleton
        {
            get { return _instance; }
        }
    }

}

An example of using the class:

C#
internal class MyObjectCache: LazyLoadCacheBase<MyObjectCache, int, IMyObject %gt;
{
    // Prevent direct instantiation
    private MyObjectCache()
    {
    }

    protected override IMyObject GetItem(int key)
    {
        
        IMyObject item = GetMyObjectFromDB(key);
        return item;
    }
}

Points of Interest

  • In order to maintain the derived classes as Singletons, they need to have a private or protected constructor. This necessitated the use of Reflection to instantiate the Singleton.
  • In my implementation, I expose the internal dictionary in case I want to load all of the items at once and to give flexibility. The required functionality should really be encapsulated (for example, a LoadAll method).
  • I have deliberately kept the class very simple. If you have additional requirements, it should be easy to add them yourself. For example, items loaded into the cache remain until the application is closed. Obviously, you could include a mechanism for removing items from the cache based on an expiry time or some other criteria.

History

  • Initial submission.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralA simpler technique Pin
PIEBALDconsult4-Sep-08 20:46
mvePIEBALDconsult4-Sep-08 20:46 
After pondering your code for a while longer I came to the conclusion that all you're really doing is wrapping the

IMyObject item = GetMyObjectFromDB(key);

to enable the caching of its results. A worthy goal in many instances, as stated in your article. So I set out to find a more elegant way than that which your article presents. Once I saw the crux of the problem I realized that a delegate would be an excellent solution.

The main part of the solution is a class to hold the cache, to instantiate it you pass in the required delegate.
(The interface isn't required, but it could come in handy at some point.)

public interface IGenericCache<TKey,TValue>
{
    TValue GetItem ( TKey Key ) ;
}
 
public class GenericCache<TKey,TValue> : IGenericCache<TKey,TValue>
{
    public delegate TValue GetItemDelegate ( TKey Key ) ;
 
    private readonly GetItemDelegate itemgetter ;
 
    private readonly System.Collections.Generic.Dictionary<TKey,TValue> cache ;
 
    public GenericCache
    (
        GetItemDelegate GetItem
    )
    {
        if ( GetItem == null )
        {
            throw ( new System.ArgumentNullException ( "GetItem" , "You must provide a delegate" ) ) ;
        }
 
        this.itemgetter = GetItem ;
 
        this.cache = new System.Collections.Generic.Dictionary<TKey,TValue>() ;
 
        return ;
    }
 
    public virtual TValue
    GetItem
    (
        TKey Key
    )
    {
        lock ( this.cache )
        {
            if ( !this.cache.ContainsKey ( Key ) )
            {
                this.cache [ Key ] = this.itemgetter ( Key ) ;
            }
 
            return ( this.cache [ Key ] ) ;
        }
    }
}


This class performs all the required functionality. I added the lock for a little thread safety, I'm not sure how effective it'll be. And as I write this I realized that maybe I should have made it IDisposible.

Usage:

GenericCache<string,int> g = new GenericCache<string,int> ( int.Parse ) ;
 
System.Console.WriteLine ( g.GetItem ( args [ 0 ] ) ) ;
System.Console.WriteLine ( g.GetItem ( args [ 0 ] ) ) ;



Or you can derive a specific cache:

public sealed class MyCache : GenericCache<string,int>
{
    public MyCache
    (
    ) : base ( int.Parse )
    {
        return ;
    }
}


You could enforce this type of derivation by making GenericCache abstract.

Usage:

MyCache m = new MyCache() ;
 
System.Console.WriteLine ( m.GetItem ( args [ 0 ] ) ) ;
System.Console.WriteLine ( m.GetItem ( args [ 0 ] ) ) ;



But that doesn't give you the singleton-ness you want, so you could also wrap one in a static class:

public static class YourCache
{
    private static readonly GenericCache<string,int> cache =
        new GenericCache<string,int> ( int.Parse ) ;
 
    public static int
    GetItem
    (
        string Key
    )
    {
        return ( cache.GetItem ( Key ) ) ;
    }
}


Usage:

System.Console.WriteLine ( YourCache.GetItem ( args [ 0 ] ) ) ;
System.Console.WriteLine ( YourCache.GetItem ( args [ 0 ] ) ) ;



Well? Whaddaya think?
GeneralRe: A simpler technique Pin
Laughing.John4-Sep-08 22:49
Laughing.John4-Sep-08 22:49 
GeneralRe: A simpler technique Pin
PIEBALDconsult5-Sep-08 4:38
mvePIEBALDconsult5-Sep-08 4:38 
GeneralRe: A simpler technique Pin
Laughing.John5-Sep-08 8:56
Laughing.John5-Sep-08 8:56 
GeneralRe: A simpler technique Pin
PIEBALDconsult5-Sep-08 10:47
mvePIEBALDconsult5-Sep-08 10:47 
GeneralRe: A simpler technique Pin
Laughing.John5-Sep-08 12:58
Laughing.John5-Sep-08 12:58 
GeneralRe: A simpler technique Pin
PIEBALDconsult5-Sep-08 13:39
mvePIEBALDconsult5-Sep-08 13:39 
GeneralRe: A simpler technique Pin
jonnii5-Sep-08 9:04
jonnii5-Sep-08 9:04 
GeneralRe: A simpler technique Pin
PIEBALDconsult5-Sep-08 10:50
mvePIEBALDconsult5-Sep-08 10:50 
AnswerRe: A simpler technique Pin
flops426-Sep-08 11:50
flops426-Sep-08 11:50 
GeneralSingletons Pin
jonnii4-Sep-08 5:33
jonnii4-Sep-08 5:33 
GeneralRe: Singletons Pin
Laughing.John4-Sep-08 6:54
Laughing.John4-Sep-08 6:54 
GeneralRe: Singletons Pin
Laughing.John4-Sep-08 7:04
Laughing.John4-Sep-08 7:04 
GeneralRe: Singletons Pin
jonnii4-Sep-08 8:01
jonnii4-Sep-08 8:01 
GeneralRe: Singletons Pin
PIEBALDconsult4-Sep-08 11:25
mvePIEBALDconsult4-Sep-08 11:25 
GeneralRe: Singletons Pin
Laughing.John4-Sep-08 14:37
Laughing.John4-Sep-08 14:37 
GeneralRe: Singletons Pin
supercat94-Sep-08 12:37
supercat94-Sep-08 12:37 
GeneralRe: Singletons Pin
Laughing.John4-Sep-08 14:29
Laughing.John4-Sep-08 14:29 
GeneralRe: Singletons Pin
jonnii5-Sep-08 9:18
jonnii5-Sep-08 9:18 
GeneralRe: Singletons Pin
PIEBALDconsult4-Sep-08 8:43
mvePIEBALDconsult4-Sep-08 8:43 
GeneralRe: Singletons Pin
Laughing.John4-Sep-08 14:43
Laughing.John4-Sep-08 14:43 
GeneralThoughts Pin
PIEBALDconsult3-Sep-08 15:55
mvePIEBALDconsult3-Sep-08 15:55 
GeneralRe: Thoughts [modified] Pin
Laughing.John3-Sep-08 22:39
Laughing.John3-Sep-08 22:39 
GeneralRe: Thoughts Pin
PIEBALDconsult4-Sep-08 3:27
mvePIEBALDconsult4-Sep-08 3:27 
GeneralRe: Thoughts Pin
Laughing.John4-Sep-08 3:39
Laughing.John4-Sep-08 3:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.