Click here to Skip to main content
15,885,985 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 78K   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

 
GeneralRe: Nice...you may also find this useful Pin
DaveyM6914-Jun-10 10:19
professionalDaveyM6914-Jun-10 10:19 
GeneralRe: Nice...you may also find this useful Pin
Sacha Barber14-Jun-10 11:12
Sacha Barber14-Jun-10 11:12 
GeneralThoughts Pin
PIEBALDconsult22-May-10 6:26
mvePIEBALDconsult22-May-10 6:26 
GeneralRe: Thoughts Pin
Pete O'Hanlon22-May-10 8:43
mvePete O'Hanlon22-May-10 8:43 
GeneralExternalException Pin
Mr.PoorEnglish22-May-10 4:12
Mr.PoorEnglish22-May-10 4:12 
GeneralRe: ExternalException Pin
leppie22-May-10 4:31
leppie22-May-10 4:31 
GeneralRe: ExternalException Pin
Alan Beasley22-May-10 5:43
Alan Beasley22-May-10 5:43 
GeneralRe: ExternalException Pin
Pete O'Hanlon22-May-10 8:53
mvePete O'Hanlon22-May-10 8:53 
What happens if you target this image with the code instead?
_image.Target = Image.FromStream(fs);
It could be the embedded colour management that's causing the problem.

"WPF has many lovers. It's a veritable porn star!" - Josh Smith

As Braveheart once said, "You can take our freedom but you'll never take our Hobnobs!" - Martin Hughes.


My blog | My articles | MoXAML PowerToys | Onyx



GeneralRe: ExternalException Pin
Pete O'Hanlon8-Jun-10 9:48
mvePete O'Hanlon8-Jun-10 9:48 
QuestionI like it - Will it help with a weak mind??? Pin
Alan Beasley22-May-10 4:11
Alan Beasley22-May-10 4:11 
AnswerRe: I like it - Will it help with a weak mind??? Pin
Pete O'Hanlon22-May-10 8:44
mvePete O'Hanlon22-May-10 8:44 
GeneralGreat article Pin
Abhinav S22-May-10 3:05
Abhinav S22-May-10 3:05 
GeneralRe: Great article Pin
Pete O'Hanlon22-May-10 8:45
mvePete O'Hanlon22-May-10 8:45 
GeneralSpreading your horizons Pin
Chris Maunder22-May-10 1:01
cofounderChris Maunder22-May-10 1:01 
GeneralRe: Spreading your horizons Pin
Pete O'Hanlon22-May-10 2:05
mvePete O'Hanlon22-May-10 2:05 
GeneralVery useful Pin
Nuri Ismail22-May-10 0:23
Nuri Ismail22-May-10 0:23 
GeneralRe: Very useful Pin
Pete O'Hanlon22-May-10 2:03
mvePete O'Hanlon22-May-10 2:03 
Generalvery interesting Pin
Luc Pattyn21-May-10 11:55
sitebuilderLuc Pattyn21-May-10 11:55 
GeneralRe: very interesting Pin
Pete O'Hanlon21-May-10 11:56
mvePete O'Hanlon21-May-10 11:56 

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.