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

WeakReferences, GCHandles, and WeakArrays

, 13 Oct 2009
Rate this:
Please Sign up or sign in to vote.
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:

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:

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:

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:

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:

~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)

About the Author

Paulo Zemek
Architect
Canada Canada
I started to program computers when I was 11 years old, as a hobbist, 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 they 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
Microsoft MVP 2013

Comments and Discussions

 
GeneralWe want the next article! + question PinmemberI'm Chris24-Mar-10 4:38 
GeneralRe: We want the next article! + question PinmemberPaulo Zemek24-Mar-10 15:51 
GeneralRe: We want the next article! + question PinmemberI'm Chris24-Mar-10 21:24 
GeneralRe: We want the next article! + question PinmemberPaulo Zemek25-Mar-10 13:44 
QuestionAbout finalizer PinmemberPuchko Vasili15-Oct-09 2:16 
AnswerRe: About finalizer PinmemberPaulo Zemek15-Oct-09 3:35 
GeneralSounds interesting Pinmembersupercat914-Oct-09 6:55 
I'm not a .net guru, so it's possible you're doing something that's nasty or icky, but it would certainly make sense that if the only persistent references to GCHandles are going to be in an array, using one finalizer for the array is going to be cheaper than having a registered finalizer for each handle.
 
That having been said, using the WeakReference type would seem to offer a substantial safety cushion against many things that could otherwise go wrong. For example, I don't know any nice way way to enforce a requirement that any function which creates a live copy of a GCHandle within the array to also "keepalive" a copy of the array. Failure to do so many result in code which will usually work, but could fail if the array happens to get garbage-collected while the reference to the GCHandle still exists.
 
I'm also interested to see how you handle a resize operation. Having to allocate new GCHandles for all the elements in an array would seem expensive, but if the old array remains valid following a resize (the usual semantics) different elements of the array could have different combinations of arrays keeping them alive. By contrast, the normal copy-on-resize semantics pose no difficulty when using individually-finalized WeakReference objects since objects are automatically kept alive by whatever array(s) they're copied into.
 
BTW, with regard to lists, I wonder whether it might make sense to use e.g. a linked list of arrays of power-of-two sizes ranging from 16 elements up to two billion (the latter arrays being allocated when the list has to grow)? Element access would be slightly slower than with a standard list (which copies everything to a single new array when things grow) but the difficulties associated with array resizing could be avoided.
GeneralRe: Sounds interesting PinmemberPaulo Zemek14-Oct-09 7:19 
GeneralRe: Sounds interesting Pinmembersupercat914-Oct-09 11:23 
GeneralRe: Sounds interesting PinmemberPaulo Zemek14-Oct-09 14:42 

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
Web01 | 2.8.140718.1 | Last Updated 13 Oct 2009
Article Copyright 2009 by Paulo Zemek
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid