Click here to Skip to main content
15,879,535 members
Articles / Programming Languages / C#

WeakReferences as a Good Caching Mechanism

Rate me:
Please Sign up or sign in to vote.
4.67/5 (4 votes)
10 Apr 2009CPOL4 min read 45.8K   490   21   12
This article shows that weak references are a good caching mechanism if used with a KeepAlive structure in mind for recently used objects.

Introduction

Many say that weak references are not a good choice for a caching mechanism, but I disagree. In my opinion, weak references are incomplete for a caching mechanism as, if you only have weak references to your cached objects, they will die at every collection. So, to solve this problem, the simplest option is to create a KeepAlive feature that guarantees that recently used objects will not be collected, and then use the weak references, as they will allow you to use your object better, as a simple "timed cache" may reload data that is still in memory.

Background

I created my first caching solution to solve the problem of sending large viewstates to clients and to avoid storing large information in sessions. The idea was simple. When a Cache<T> object was created, it serialized its target object in a temporary directory. The cleanup of such a directory or how I did it to be fast is not the case. When I didn't have the information in memory, I loaded it from the serialized data. When I had it in memory, I simply reused it. But, after a collection, I needed to reload all the cached objects that were actively used. So, I implemented the first version of the GCUtils class, with a method named KeepAlive that allowed an object to live for 30 seconds after its last use, thus surviving collections during this time. Now, I use a much better version that allows the object to survive only to the next collection. After that, if a new KeepAlive is not done, the second collection will collect it.

Using the Code

I have uploaded a zip file with this article with the source code of a DLL of my own, and a sample that shows how to use KeepAliveWeakReference, WeakDictionary, and WeakKeyDictionary. The DLL also has some other useful classes, but for this article, these three classes are the most important ones.

The GCUtils has two important items. The first is the Collected event. I use this method in my WeakDictionary and WeakKeyDictionary to remove collected references. Also, if you see my implementation, I have used the GCHandle for WeakDictionary and WeakKeyDictionary, but that's because the GCHandle is the class that really does the weak reference work, but it is "unsafe" to use as it can leak memory if used improperly.

The Collected event is very useful. For example, you can add a handler to the Collected event to do a TrimExcess in your lists or hashsets. After all, a weak reference will not allow you to free memory by doing a TrimExcess, right?

But, Collected works in another thread, so the collected handler must be thread safe. For example, in the class constructor, you call:

C#
GCUtils.Collected += p_Collected;

And then, you implement p_Collected like:

C#
private void p_Collected()
{
    lock(DisposeLock)
    {
        if (WasDisposed)
            return;

        GCUtils.Collected += p_Collected;

        var oldDictionary = fDictionary;
        var newDictionary = new Dictionary<TKey, ExtendedGCHandle>();
        foreach(var pair in oldDictionary)
        {
            var wr = pair.Value;

            if (wr.IsAlive)
                newDictionary.Add(pair.Key, pair.Value);
            else
                wr.Free();
        }

        fDictionary = newDictionary;
    }
}

This is the code used by WeakDictionary to remove unused items. I must re-register for the Collected event, as the collected list is cleared when it runs. The really important part here is to see the use of the lock keyword, as the entire class must access its dictionary using a lock.

And, the other important item of GCUtils is the KeepAlive method. The GC.KeepAlive is not the same. GC.KeepAlive keeps an object alive until that specific line of code. GCUtils.KeepAlive keeps an object alive for the next collection.

The other interesting classes are KeepAliveWeakReference, WeakDictionary, and WeakKeyDictionary.

KeepAliveWeakReference is a WeakReference inheritor that simple calls KeepAlive every time its target is got. So, you can use only such WeakReference as the reference to the item you want to cache (for example, the background image used in the example of how to use WeakReferences in the MSDN help), and you guarantee that it will not be collected while you are constantly re-rendering the background, even if a collection happens. And, if it stops being used, then it will die after the second collection.

For example, declare a variable like:

C#
var weakReference = new KeepAliveWeakReference<byte[]>(bytes);

where bytes can be an array of many MB. And then, you access it like:

C#
byte[] bytes = weakReference.Target;

Doing this already calls KeepAlive for you. So, if bytes is not null, you can use it safely.

WeakDictionary is effectively a dictionary where values can be collected. It does not use WeakReferences, using the GCHandle directly for performance, but the idea is the same. And, in the Collected event, it removes the values that becomes null.

WeakKeyDictionary allows the keys to be collected. This is useful if you want to "add" fields to existing classes. For example, using extension methods, I add the GetTag and SetTag (simulating the Tag property that I used in Delphi) to any object. But, if I use a dictionary to do this, I will not allow an object with a tag to be collected. Using WeakKeyDictionary, I allow this.

Here is the source code. I simply use a WeakKeyDictionary to store the tag value. I consider the default value to be -1, so if the object does not have a tag, -1 is returned, and setting -1 to the tag value will remove it from the dictionary. The way the dictionary works, while the object is alive, its tag is alive. Just after it dies, the tag in the dictionary dies too.

C#
public static class PropertyExtender
{
    private static WeakKeyDictionary<object, int> fTags = 
            new WeakKeyDictionary<object,int>();

    public static int GetTag(this object obj)
    {
        int result;
        if (fTags.TryGetValue(obj, out result))
            return result;
        return -1;
    }

    public static void SetTag(this object obj, int tagValue)
    {
        if (tagValue == -1)
            fTags.Remove(obj);
        else
            fTags[obj] = tagValue;
    }
}

History

  • 7th April, 2009: Initial post
  • 8th April, 2009: New version of the library and sample

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) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
QuestionIs it a bug -- WeakCollection? Pin
Johnny_Liu4-Jun-14 3:21
Johnny_Liu4-Jun-14 3:21 
AnswerRe: Is it a bug -- WeakCollection? Pin
Paulo Zemek4-Jun-14 3:42
mvaPaulo Zemek4-Jun-14 3:42 
GeneralRe: Is it a bug -- WeakCollection? Pin
Johnny_Liu4-Jun-14 3:58
Johnny_Liu4-Jun-14 3:58 
GeneralRe: Is it a bug -- WeakCollection? Pin
Paulo Zemek4-Jun-14 6:03
mvaPaulo Zemek4-Jun-14 6:03 
GeneralRe: Is it a bug -- WeakCollection? Pin
Johnny_Liu4-Jun-14 13:33
Johnny_Liu4-Jun-14 13:33 
GeneralRe: Is it a bug -- WeakCollection? Pin
Paulo Zemek4-Jun-14 14:31
mvaPaulo Zemek4-Jun-14 14:31 
Questionis using weakreference to cache is good pratice? Pin
codezyc7-Oct-13 17:54
codezyc7-Oct-13 17:54 
AnswerRe: is using weakreference to cache is good pratice? Pin
Paulo Zemek8-Oct-13 4:04
mvaPaulo Zemek8-Oct-13 4:04 
GeneralThread-safety [modified] Pin
Paulo Zemek10-Apr-09 8:50
mvaPaulo Zemek10-Apr-09 8:50 
QuestionHow does it compare to LRUCache on CodeProject? Pin
I'm Chris7-Apr-09 21:31
professionalI'm Chris7-Apr-09 21:31 
AnswerRe: How does it compare to LRUCache on CodeProject? Pin
Paulo Zemek8-Apr-09 7:48
mvaPaulo Zemek8-Apr-09 7:48 
AnswerRe: How does it compare to LRUCache on CodeProject? Pin
Paulo Zemek8-Apr-09 8:07
mvaPaulo Zemek8-Apr-09 8:07 

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.