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

Low impact images

, 8 Jun 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Pete O'Hanlon
CEO
United Kingdom United Kingdom
A developer for over 30 years, I've been lucky enough to write articles and applications for Code Project as well as the Intel Ultimate Coder - Going Perceptual challenge. I live in the North East of England with 2 wonderful daughters and a wonderful wife.
 
I am not the Stig, but I do wish I had Lotus Tuned Suspension.
Follow on   Twitter   Google+

Comments and Discussions

 
GeneralMy vote of 4 PinprofessionalPaulo Zemek8-Oct-13 5:24 
GeneralRe: My vote of 4 PinprotectorPete O'Hanlon8-Oct-13 5:30 
GeneralRe: My vote of 4 PinprofessionalPaulo Zemek8-Oct-13 5:41 
GeneralRe: My vote of 4 PinprotectorPete O'Hanlon8-Oct-13 5:47 
GeneralRe: My vote of 4 PinprofessionalPaulo Zemek8-Oct-13 5:50 
GeneralRe: My vote of 4 PinprotectorPete O'Hanlon8-Oct-13 5:51 
GeneralAwesome sauce Pete! 5 from me PinmemberGreg Cadmes19-Nov-12 5:06 
GeneralRe: Awesome sauce Pete! 5 from me PinprotectorPete O'Hanlon19-Nov-12 5:16 
GeneralMy vote of 3 Pinmemberkishhr21-Jun-12 8:01 
GeneralRe: My vote of 3 PinprotectorPete O'Hanlon21-Jun-12 11:19 
GeneralRe: My vote of 3 Pinmemberkishhr22-Jun-12 2:46 
GeneralRe: My vote of 3 PinprotectorPete O'Hanlon26-Jun-12 2:46 
GeneralI've got a question PinmemberJecka7-Jun-10 21:43 
GeneralRe: I've got a question PinmvpPete O'Hanlon8-Jun-10 10:57 
GeneralIt's a gotcha! :) PinmvpDaniel Vaughan29-May-10 6:50 
GeneralRe: It's a gotcha! :) PinmvpPete O'Hanlon29-May-10 10:48 
GeneralRe: It's a gotcha! :) PinmvpPete O'Hanlon8-Jun-10 10:49 
GeneralGood Article PinmemberRichard Blythe24-May-10 14:34 
GeneralRe: Good Article PinmvpPete O'Hanlon24-May-10 23:03 
GeneralExcellent, Pete PinmemberMarcelo Ricardo de Oliveira24-May-10 12:52 
GeneralRe: Excellent, Pete PinmvpPete O'Hanlon24-May-10 12:53 
GeneralInteresting PinmemberBryanWilkins24-May-10 4:10 
GeneralRe: Interesting PinmvpPete O'Hanlon24-May-10 4:12 
GeneralToo easy to miss the race condition PinmemberRama Krishna Vavilala23-May-10 15:35 
GeneralRe: Too easy to miss the race condition PinmvpPete O'Hanlon23-May-10 22:57 

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 | Terms of Use | Mobile
Web01 | 2.8.141216.1 | Last Updated 8 Jun 2010
Article Copyright 2010 by Pete O'Hanlon
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid