Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Low impact images

0.00/5 (No votes)
8 Jun 2010 4  
This article describes one way of dealing with the thorny issue of image lifetime in applications.

Introduction

A couple of days ago, somebody posted a question in one of the forums about who had the responsibility for disposing of an image when it was used in more than one place. In this particular case, the poster had an image that was used in a user control as well as displayed on a form. This is a fairly common problem, and there's a fairly simple solution that makes use of a handy .NET feature called a weak reference.

Weak references

Before we delve into the code, I'd like to cover some old ground. If you are familiar with weak references, then feel free to skip this section - if you want to find out what the weak reference gotcha is, then please read on.

As you know, .NET is a garbage collected language which normally means that we don't have to worry about memory; when memory is no longer used, it's possible for the garbage collector to free it up. So far, this should be nothing new to you.

Now, it's not always possible for .NET to free everything up because there could well be unmanaged code that needs to be freed up and this stuff builds up over time - and these are removed in well written apps by classes that implement IDisposable (okay, this is a simplification, but it is a useful way to envision the process). What happens, though, if you want to deallocate some memory to an object that hasn't been dereferenced? If your application has a strong reference to an object, it won't be garbage collected.

The answer to this is to use a WeakReference. A weakly referenced object can be garbage collected at any time, so the memory can be freed up, with the understanding that we can get a strong reference to the object at some point, which means that the garbage collector will no longer attempt to pick it up.

Well, that's enough theory - let's look at how this works in practice.

private WeakReference _image = new WeakReference(null);
using (FileStream fs = info.OpenRead())
{
  try
  {
    if (_image == null)
      _image = new WeakReference(null);
    _image.Target = Image.FromStream(fs, true, false);
    fs.Close();
  }
  catch (Exception ex)
  {
    throw new ArgumentException("Unable to load the image file", ex);
  }
}

In the above code, we've created a weakly referenced object (_image), which we use to load an image into. The garbage collector can pick this information up at any time, but we have it to use if it's still present. So, how do we do this?

The observant will have noticed that the image is actually stored in the target of the weak reference. In other words, it is not the weak reference itself - the target is the item we are interested in, and this suggests that we should be able to use the WeakReference itself to determine whether or not the image has been collected (or to put it another way, our application will have a strong reference to the WeakReference object, which maintains the weak reference to the image - this means that the collector can't collect the WeakReference object, but it can collect the image). Anyway, this is one way of determining whether or not the item has not been garbage collected:

if (_image.IsAlive)
{
  Image strongReference = _image.Target as Image;
}

IsAlive tells us that the image hasn't been garbage collected and the strongReference line creates a strong reference to the image. Sounds good, doesn't it? Well, this is the bit that has the gotcha that I was talking about. Have you seen it? Here's the code re-jigged slightly, which should illustrate the point:

Image strongReference = _image.Target as Image;
if (strongReference == null)
{
  using (FileStream fs = info.OpenRead())
  {
    try
    {
      _image.Target = Image.FromStream(fs, true, false);
     fs.Close();
    }
    catch (Exception ex)
    {
      throw new ArgumentException("Unable to load the image file", ex);
    }
  }
  strongReference = _image.Target as Image;
}

Yup, rather than checking IsAlive and then picking up the reference, we use the reference itself to determine whether or not it has been garbage collected. In other words, we can't rely on IsAlive here because there's a potential race condition where the garbage collector removes the reference between IsAlive and the allocation from the Target.

ImageManager

In the attached solution(s), we have an image manager class that we use to add the images to (cunningly enough), and this class manages the lifetime of the image. This means that we've largely removed the problem of deciding where the items should be disposed of.

The images are notionally held in a dictionary, and can be referenced using the key. In reality, the images are held in a separate ImageCache class which maintains the image as a weak reference, and the ImageManager provides access to this. All access to the images are managed via this class, which is implemented as a singleton.

Adding an image is simple enough - here's the code from the enclosed form that demonstrates adding an image and displaying it when an item is selected in the listbox:

private void btnFile_Click(object sender, EventArgs e)
{
  using (FileDialog dialog = new OpenFileDialog())
  {
    dialog.CheckFileExists = true;
    dialog.ValidateNames = true;
    dialog.Filter = "All files (*.*)|*.*";
    if (dialog.ShowDialog() == DialogResult.OK)
    {
      string file = dialog.FileName;
      string key = txtKey.Text;
      if (string.IsNullOrEmpty(key))
      {
        key = Guid.NewGuid().ToString();
      }
      ImageManager.Instance.Add(key, file);
      listKeys.Items.Add(key);
      ShowImage(key);
    }
  }
}
private void ShowImage(string key)
{
  displayImage.Image(key);
}
private void KeyChosen(object sender, EventArgs e)
{
  string key = listKeys.SelectedItem.ToString();
  ShowImage(key);
}

When we return the image out of our ImageManager class, what we get back isn't the original image, it's a clone of it. The reason we have the ImageCache class is that we need to keep the path referenced so that we can resurrect the image if necessary by loading a new instance of it.

Final thoughts

I know this isn't my usual advocacy of WPF. There's no WPF code in this sample, and there's a reason for that; WPF was designed around the idea of weak references, so what I'm showing here is common practice in the WPF world. Please feel free to play around with the code; use it if it's useful - ignore it if it's not.

History

  • 8 June 2010 - Updated the code to raise more appropriate exceptions, handle certain files that throw a GDI+ exception, and to use Sacha Barber's excellent generic WeakReference implementation (thanks Sacha).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here