Click here to Skip to main content
Click here to Skip to main content

Using C# Generics to implement a Cache collection

, 10 Jul 2004 CPOL
Rate this:
Please Sign up or sign in to vote.
An article on how I used Generics to implement a general purpose caching collection with user defined size limits.

Sample Image - GenericCache.jpg

Overview

This article really started out of a desire for me to learn the new generics functionality added to the newer versions of .NET and the version 2.0 of the Framework. Whilst at this moment I don't feel that a full fledged tutorial on generics is appropriate, this article shows my use of generics in solving a problem I recently encountered.

Introduction

In my current job, I recently had the misfortune to discover a memory issue with our main development. We use hundreds of bitmaps at any one time. Long ago, someone thought that in order to improve performance we would cache the bitmaps in memory. Nice idea, except my current project seems to have pushed the technology further than anyone else had, and now we're out of memory because of the hundreds of Megabytes of images we have cached.

The Solution?

Thinking about the problem, it was obvious that we didn't need to cache every image, just the ones we were working with. Trying the system without any caching demonstrated why the caching support was originally added, performance started to get sluggish as the hard-disk is accessed so much. However, if an image hasn't been used for ages, then we can quite easily read that again from disk (as we had first time it was used) keeping the frequently and recently accessed images in the cache.

At work, a couple of hours of coding and I had implemented a very basic C++ cache using the STL and templates. This article however takes over from the point of me wondering how much alike the STL and the new C# generics are.

Generics

One of the things I really disliked about coding in C# was that whenever I used a collection (which I did lots) I had to worry about the types I was adding and retrieving, or else I had to create my own type-safe implementations of the collections. Whenever I use the STL in C++, this was never an issue. Constantly checking and casting objects was cluttering up my code.

On the most basic level, generics allow me to specify the types I want to be working with. Take the following two examples, the first in C# 1.x and the second in C# 2.x.

ArrayList items = new ArrayList();
items.Add("a string");
items.Add(123);
items.Add(this);
List<string> items = new List<string>();
items.Add("a string");
items.Add(123);  // Compile time error!!
items.Add(this);  // Compile time error!!

Using generics, I can specify the type that I want to have a list of, in this case strings. The compiler will then ensure that I do not start adding lots of incompatible types.

Cache<Key,Value>

The implementation of the cache that I have created uses the signature of cache<Key,Value> which looks very similar to the signature of a hashtable or dictionary. What I wanted is to associate the cached data with some form of identifier (e.g., the filename of the bitmap I have cached). The actual class definition is shown below:

   public class Cache 
      where Key: IComparable
      where Value: ICacheable
   {

Notice that there are some changes to the typical class definition. The new where keyword allows the use of generics to be refined. In the code above, I have specified that I want the Key type to support at least IComparable (this is necessary for me to search for the keys in the collections).

I have also defined a need for the values to implement ICacheable. The ICacheable interface is a very simple interface I created:

 public interface ICacheable
 {
  int BytesUsed { get; }
 }

All it really needs to do is supply the property BytesUsed in order to let the cache know how big its represented data is. This can be seen being accessed in the Add method.

  public void Add(Key k, Value v)
  {
    // Check if we're using this yet
    if (ContainsKey(k))
    {
      // Simple replacement by removing and adding again, this
      // will ensure we do the size calculation in only one place.
      Remove(k);
    }

    // Need to get current total size and see if this will fit.
    int projectedUsage = v.BytesUsed 
                       + this.CurrentCacheUsage;
      
    if (projectedUsage > maxBytes)
      PurgeSpace(v.BytesUsed);

    // Store this value now..
    StoreItem(k, v);
  }

As is typical with any container class, there are a number of other methods for getting or changing the members of the collection. Implemented methods are:

  • void Remove(Key k)
  • void Touch(Key k)
  • Value GetValue(Key k)
  • bool ContainsKey(Key k)
  • Value this[Key k]
  • void PurgeAll()

Enumerators

In order to access the stored members of the cache, a number of enumerators are implemented, these allow getting the keys, the values, and both the key and value as a pair. The implementation of enumerators in C# 2.0 has been made very easy.

  • IEnumerator<Value> GetEnumerator()
  • IEnumerable<Key> GetKeys()
  • IEnumerable<Value> GetValues()
  • IEnumerable<KeyValuePair<Key, Value>> GetItems()
 public IEnumerable<Value> GetValues()
 {
  foreach (KeyValuePair<Key, Value> i in cacheStore)
   yield return i.Value;
 }

Note the new yield keyword. This, in effect, allows the control of the program execution to be passed back to (or yielded to) the caller. Next time the enumerator is accessed, the execution continues at the same point in the collection. In the above example, I am accessing the internal store, cacheStore, and enumerating it with the foreach command, however, I am exposing the value of each to the caller of GetValues(). Whilst this might take a little understanding, trust me, it is simpler than what was needed before!

Properties

There are only a few properties defined for the cache class, their functionality is largely obvious.

  • int MaxBytes { get; set; }
  • int Count{ get; } // Get the current number of items in cache
  • int CurrentCacheUsage{ get; }

Performance

As a condition of using the beta software from Microsoft, it is not possible to quote performance of this class in concrete terms. The compiler and its generated code is likely to get much faster in the future… also, it isn't really possible to directly compare generic code to non-generic code without having two different implementations. I have tested this code with many Megabytes of bitmaps and its caching performance was as good, no noticeable impact on performance. It must be noted that once an item is knocked out of the cache, the run-time system and its garbage collection may not dispose off the object for quite a while. What the cache implementation does do is allow you to control the lifespan of your objects!

Conclusion

The implementation of the cache class used here is very basic; it could be further enhanced in many ways. For my needs, the solution implemented is not too bad and allows me some control over how much memory I want my applications using in order to keep large resources active in memory.

Visual Studio 2005 and C# 2.0

At the time of writing, C# 2.0 is very new. I downloaded the beta from Microsoft only last week. If in the future, this article is still around, be careful to research any differences between what I have documented here and the final release!

If you want to download a copy of the beta, see MSDN.

I have not included a binary of this code because I expect the framework to change between each beta. The two C# source files supplied comprise of the implementation and the test application, drop them into a blank console solution to test!

License

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

Share

About the Author

Ray Hayes
Product Manager
United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
GeneralA small typo PinmemberNemanja Trifunovic12-Jul-04 5:32 
GeneralRe: A small typo PinmemberRay Hayes12-Jul-04 5:53 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.141022.2 | Last Updated 11 Jul 2004
Article Copyright 2004 by Ray Hayes
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid