Click here to Skip to main content
15,867,704 members
Articles / Programming Languages / C#

WeakReferences, GCHandles, and WeakArrays

Rate me:
Please Sign up or sign in to vote.
4.80/5 (12 votes)
13 Oct 2009CPOL5 min read 36.8K   22   10
This article shows the differences of WeakReferences and GCHandles and how to implement a WeakArray.

Introduction

For those who don't know yet, Weak References are references to objects that still allow such objects to be collected, then becoming null. By default, every reference used when you program is a Strong-Reference. That is, such a reference will not allow the other object to die, considering such a reference is not "dead" itself.

But the purpose here is not to explain exactly how Weak References work. It's to explain the difference between Weak References and GCHandles, and why GCHandles can be better in some situations.

What is a GCHandle?

It looks redundant, but internally, the WeakReference class uses some GCHandle internal methods to do its job. But we can simplify this by saying that the WeakReference is a GCHandle wrapper with a destructor.

The GCHandle has other uses but, for the weak-reference part, it is a ValueType "unsafe" reference to the object. I say "unsafe" because it does not require you to be in unsafe mode to be used, but its possibility of memory leaks makes it unsafe.

Why exactly is it unsafe?

Because it does not have a destructor. You may think that allowing the object to die will never cause memory-leaks, but the reference itself will be lost forever if you forget to free it. It does not leak the same amount of memory as leaking an entire object, but will be a memory-leak the same way. Using a Weak Reference will avoid this completely.

So, why would anyone ever consider using it?

Performance. When you create only one Weak Reference to a large image, for example, you will never notice any performance penalty for using the WeakReference class. But, think about creating a Cache framework, which will store thousands of "cached objects".

If you use Weak References, you will in fact:

  • Have 10000 strong-references to the Weak References;
  • Have 10000 GCHandles inside these Weak References;
  • And then have the 10000 objects (or nulls for those not used or already collected).

One important thing here is:

Weak References have destructors. And so, when the memory is collected, the Weak References themselves will live to the next generation. If this is a Generation 0 collection, the WeakReference objects will live for the Generation 1 collection. We can have many Generation 0 collections before the Generation 1 collection arrives.

If we use GCHandles directly, we will have:

  • 10000 GCHandles;
  • And then have the 10000 objects (or nulls for those not used or already collected).

Well, at this point, I think I will explain Weak Arrays. .Net does not come with WeakLists, WeakArrays, or anything similar. To implement lists, we internally need to have arrays, and the first step is to implement WeakArrays.

For example:

C#
WeakReference[] weakReferenceArray = new WeakReference[10000];

The previous line creates an array of weak references (not a WeakArray). It has 10000 positions, to store Weak References, but those Weak References will need to be initialized some-time. I will initialize them immediately with:

C#
for (int i=0; i<10000; i++)
  weakReferenceArray[i] = new WeakReference(null);

Well, I still don't have any real objects here, but I already have an array with 10000 strong-references to 10000 Weak Reference objects.

Let's see the GCHandle version:

C#
GCHandle[] gcHandles = new GCHandle[10000];
for(int i=0; i<10000; i++)
  gcHandles[i] = GCHandle.Alloc(null, GCHandleType.Weak);

Well, the code is very similar but, in fact, we have only an array with 10000 references that are already GCHandles (as they are value types). We don't have 10000 objects with 10000 finalizers. But, that's the main point. If we simple do this, even if the array is collected, the 10000 "handles" will be lost. So, different from the first example, we need to implement a destructor. Of course, we can create only one destructor to free all handles. This is the main advantage.

So, our first version of the WeakArray class:

C#
public class WeakArray
{
  private GCHandle[] fArray;
  public WeakArray(int length)
  {
    fArray = new GCHandle[length];
    for (int i=0; i<length; i++)
      fArray[i] = GCHandle.Alloc(null, GCHandleType.Weak);
  }
  ~WeakArray()
  {
    int count = fArray.Length;
    for(int i=0; i<count; i++)
      fArray[i].Free();
  }
  
  public int Length
  {
    get
    {
      return fArray.Length;
    }
  }
  public object this[int index]
  {
    get
    {
      return fArray[index].Target;
    }
    set
    {
      fArray[index].Target = value;
    }
  }
}

Well, now we have an almost leak-free WeakArray. Why almost? If we can't allocate the requested length, we will generate an exception. But, that's not the problem, the user may treat the exception. The destructor will also be invoked and we don't test if the array or the references are valid. Calling Free on an unitialized handle will throw an exception. But, still, if the constructor was completed, the finilizer is OK.

Why am I showing the incomplete code? Because it looks like it is fine. That's the most important thing to be aware of. Now, let's see the right destructor:

C#
~WeakArray()
{
  if (fArray == null)
    return;
    
  int count = fArray.Length;
  for(int i=0; i<count; i++)
  {
    GCHandle gcHandle = fArray[i];
    
    if (!gcHandle.IsAllocated)
      break;
      
    gcHandle.Free();
  }
}

First thing: Check if the array is null. If it is, then we can simple return, as we don't allocate the array.

Second thing: Loop over all items. On we reach the first one that is not allocated, we stop. Why? Because we allocate them in order. If the 10th is not allocated, 11th, 12th, and the rest will also not be allocated.

Now, I can say we have a working WeakArray. But, is it really complete? The answer is No.

We didn't implement the Dispose method. The idea is to have a Dispose method for any method that has a destructor. But, it is not that simple. To guarantee a non-leaking WeakArray which disposes, we would need to make the array thread-safe (or will need to make another class, like a ThreadSafeWeakArray). The actual one doesn't need anything special to be thread-safe, as the GCHandle itself is thread-safe, and the full allocation and deallocation are done only in the constructor and destructor, which will always run on a single thread. We could make the array only initialize the handles when they are first needed. But this will, again, need to care about Thread-Safety. And finally, if we want to make the arrays to use them in lists, we need to think about how lists work: Initially, they allocate some amount of space. Let's say 8. Then, when we add the ninth item, they are reallocated, to the size of 16, and we fill the item 9.

Well, how will the reallocation work?

If we have a WeakArray of 8 and we allocate another WeakArray of 16, we will have 24 weak-references. Maybe we can simply create a "Resize" method that reallocates the fArray object, and then we only initialize GCHandles from 9 to 16. But, of course, I am again forgetting the "allocate only when needed" behavior. Complex, isn't? I will leave this for another article. For now, this simple WeakArray implementation is the best one to be shown. At least you will only have one finalizer (and one real object) for all the weak-GCHandles, instead of having one object and finalizer for each.

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

 
GeneralWe want the next article! + question Pin
I'm Chris24-Mar-10 4:38
professionalI'm Chris24-Mar-10 4:38 
GeneralRe: We want the next article! + question Pin
Paulo Zemek24-Mar-10 15:51
mvaPaulo Zemek24-Mar-10 15:51 
GeneralRe: We want the next article! + question Pin
I'm Chris24-Mar-10 21:24
professionalI'm Chris24-Mar-10 21:24 
GeneralRe: We want the next article! + question Pin
Paulo Zemek25-Mar-10 13:44
mvaPaulo Zemek25-Mar-10 13:44 
QuestionAbout finalizer Pin
Puchko Vasili15-Oct-09 2:16
Puchko Vasili15-Oct-09 2:16 
AnswerRe: About finalizer Pin
Paulo Zemek15-Oct-09 3:35
mvaPaulo Zemek15-Oct-09 3:35 
GeneralSounds interesting Pin
supercat914-Oct-09 6:55
supercat914-Oct-09 6:55 
GeneralRe: Sounds interesting Pin
Paulo Zemek14-Oct-09 7:19
mvaPaulo Zemek14-Oct-09 7:19 
GeneralRe: Sounds interesting Pin
supercat914-Oct-09 11:23
supercat914-Oct-09 11:23 
GeneralRe: Sounds interesting Pin
Paulo Zemek14-Oct-09 14:42
mvaPaulo Zemek14-Oct-09 14:42 

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.