Click here to Skip to main content
15,881,588 members
Articles / Programming Languages / C# 4.0

Low impact images

Rate me:
Please Sign up or sign in to vote.
4.89/5 (44 votes)
8 Jun 2010CPOL5 min read 77.9K   890   57   52
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.

C#
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:

C#
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:

C#
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:

C#
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)


Written By
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.

Comments and Discussions

 
Generalvery useful Pin
Southmountain30-Nov-19 15:21
Southmountain30-Nov-19 15:21 
GeneralMy vote of 4 Pin
Paulo Zemek8-Oct-13 4:24
mvaPaulo Zemek8-Oct-13 4:24 
The low impact images is good and the code works very well for some images. But try loading 1000 or more images and, even if you have memory, images will be removed from your cache because there is nothing trying to keep them alive. Also, it will be better to load and return the strong reference instead of loading the image and putting it to the weak reference. Even if it is very rare, it is possible that between the return of the load function and the access to the WeakReference.Target the image is already collected.
GeneralRe: My vote of 4 Pin
Pete O'Hanlon8-Oct-13 4:30
mvePete O'Hanlon8-Oct-13 4:30 
GeneralRe: My vote of 4 Pin
Paulo Zemek8-Oct-13 4:41
mvaPaulo Zemek8-Oct-13 4:41 
GeneralRe: My vote of 4 Pin
Pete O'Hanlon8-Oct-13 4:47
mvePete O'Hanlon8-Oct-13 4:47 
GeneralRe: My vote of 4 Pin
Paulo Zemek8-Oct-13 4:50
mvaPaulo Zemek8-Oct-13 4:50 
GeneralRe: My vote of 4 Pin
Pete O'Hanlon8-Oct-13 4:51
mvePete O'Hanlon8-Oct-13 4:51 
GeneralAwesome sauce Pete! 5 from me Pin
Greg Cadmes19-Nov-12 4:06
Greg Cadmes19-Nov-12 4:06 
GeneralRe: Awesome sauce Pete! 5 from me Pin
Pete O'Hanlon19-Nov-12 4:16
mvePete O'Hanlon19-Nov-12 4:16 
GeneralMy vote of 3 Pin
kishhr21-Jun-12 7:01
kishhr21-Jun-12 7:01 
GeneralRe: My vote of 3 Pin
Pete O'Hanlon21-Jun-12 10:19
mvePete O'Hanlon21-Jun-12 10:19 
GeneralRe: My vote of 3 Pin
kishhr22-Jun-12 1:46
kishhr22-Jun-12 1:46 
GeneralRe: My vote of 3 Pin
Pete O'Hanlon26-Jun-12 1:46
mvePete O'Hanlon26-Jun-12 1:46 
GeneralI've got a question Pin
Jecka7-Jun-10 20:43
Jecka7-Jun-10 20:43 
GeneralRe: I've got a question Pin
Pete O'Hanlon8-Jun-10 9:57
mvePete O'Hanlon8-Jun-10 9:57 
GeneralIt's a gotcha! :) Pin
Daniel Vaughan29-May-10 5:50
Daniel Vaughan29-May-10 5:50 
GeneralRe: It's a gotcha! :) Pin
Pete O'Hanlon29-May-10 9:48
mvePete O'Hanlon29-May-10 9:48 
GeneralRe: It's a gotcha! :) Pin
Pete O'Hanlon8-Jun-10 9:49
mvePete O'Hanlon8-Jun-10 9:49 
GeneralGood Article Pin
Richard Blythe24-May-10 13:34
Richard Blythe24-May-10 13:34 
GeneralRe: Good Article Pin
Pete O'Hanlon24-May-10 22:03
mvePete O'Hanlon24-May-10 22:03 
GeneralExcellent, Pete Pin
Marcelo Ricardo de Oliveira24-May-10 11:52
Marcelo Ricardo de Oliveira24-May-10 11:52 
GeneralRe: Excellent, Pete Pin
Pete O'Hanlon24-May-10 11:53
mvePete O'Hanlon24-May-10 11:53 
GeneralInteresting Pin
BryanWilkins24-May-10 3:10
professionalBryanWilkins24-May-10 3:10 
GeneralRe: Interesting Pin
Pete O'Hanlon24-May-10 3:12
mvePete O'Hanlon24-May-10 3:12 
GeneralToo easy to miss the race condition Pin
Rama Krishna Vavilala23-May-10 14:35
Rama Krishna Vavilala23-May-10 14:35 

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.