ImageList indexer returns Copy of Image instead of Reference

K

KenN

We're using a System.Windows.Forms.ImageList to store a bunch of Images that
I've loaded, and rendering each image in the ImageList during a draw loop.
The process was running unusually slow and was causing memory spikes. After
several hours we discovered that the ImageList.Images indexer is actually
returning a copy of the added Images instead of simply returning a reference.
Needless to say, we were a bit surprised and disturbed to discover this.

1) Can anyone answer why the ImageList collection returns a copy instead of
a reference?

2) Is there a way to get the reference instead of a copy?

If you run the example code below, the output to the console window will be
"Different Image".

// Example Code

System.Windows.Forms.ImageList imageList =
new System.Windows.Forms.ImageList();

System.Drawing.Image image =
System.Drawing.Image.FromFile(@"C:\image.png");

imageList.Images.Add(image);

// Get the same Image twice!
System.Drawing.Image imageFromList1 = imageList.Images[0];
System.Drawing.Image imageFromList2 = imageList.Images[0];

if (object.ReferenceEquals(imageFromList1, imageFromList2))
{
Console.WriteLine("Same Image");
}
else
{
Console.WriteLine("Different Image");
}

Console.ReadLine();
 
M

Marc Gravell

Can you give more info on the setup? For example, would it be possible
to simply switch to a "List<Image>"? or are you using the ImageList to
supply other controls?
 
N

Nicholas Paldino [.NET/C# MVP]

KenN,

There is no way to get the original reference. If you want to keep the
same reference, you have to do it yourself.

This behavior is documented in the Item property for the
ImageList.ImageCollection class:

http://msdn2.microsoft.com/en-us/library/bz38zyat.aspx

I assume you are ^not^ calling Dispose on the image returned after using
it, because your assumption was that you were sharing the reference. You
might see some improvement if you dispose of the image when done with it
(assuming you aren't passing it to a control and transferring management of
the lifetime to the control).
 
K

KenN

We're using a third party framework that is rendering the Image.
Unfortunately, it requires the use of an ImageList, and an index into the
ImageList. We currently have worked around the problem by not using this
feature of the third party framework (using the List<Image> like you
suggested), but it's not an ideal solution.

Thanks!
 
K

KenN

Really, this is more a of philosophical question of Why doesn't this
collection behave like other .NET collections. One would think that a
collection of Images (which can be relatively large objects) would default to
return a reference rather than returning a copy, especially when the
ImageList is part of System.Windows.Forms. (Meaning, hey this is a UI
collection, maybe this should be as fast as possible?).

Nicholas Paldino said:
KenN,

There is no way to get the original reference. If you want to keep the
same reference, you have to do it yourself.

This behavior is documented in the Item property for the
ImageList.ImageCollection class:

http://msdn2.microsoft.com/en-us/library/bz38zyat.aspx

I assume you are ^not^ calling Dispose on the image returned after using
it, because your assumption was that you were sharing the reference. You
might see some improvement if you dispose of the image when done with it
(assuming you aren't passing it to a control and transferring management of
the lifetime to the control).


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

KenN said:
We're using a System.Windows.Forms.ImageList to store a bunch of Images
that
I've loaded, and rendering each image in the ImageList during a draw loop.
The process was running unusually slow and was causing memory spikes.
After
several hours we discovered that the ImageList.Images indexer is actually
returning a copy of the added Images instead of simply returning a
reference.
Needless to say, we were a bit surprised and disturbed to discover this.

1) Can anyone answer why the ImageList collection returns a copy instead
of
a reference?

2) Is there a way to get the reference instead of a copy?

If you run the example code below, the output to the console window will
be
"Different Image".

// Example Code

System.Windows.Forms.ImageList imageList =
new System.Windows.Forms.ImageList();

System.Drawing.Image image =
System.Drawing.Image.FromFile(@"C:\image.png");

imageList.Images.Add(image);

// Get the same Image twice!
System.Drawing.Image imageFromList1 = imageList.Images[0];
System.Drawing.Image imageFromList2 = imageList.Images[0];

if (object.ReferenceEquals(imageFromList1, imageFromList2))
{
Console.WriteLine("Same Image");
}
else
{
Console.WriteLine("Different Image");
}

Console.ReadLine();
 
P

Peter Duniho

Really, this is more a of philosophical question of Why doesn't this
collection behave like other .NET collections.

Well, I'm not sure it doesn't behave like _any_ other .NET collections.

Some collections are general-purpose, or meant specifically to control
some collection aspect of an object. In those cases, obviously you
wouldn't have a collection returning copies of its contents, because you
specifically want those things to be mutable.

However, there are plenty of other situations in which you don't want some
other code be able to modify the data you're holding. In these
situations, you need to return a copy of reference types so that the code
you gave the reference to can't fiddle with the thing you have yourself.
I think it's not unreasonable that the ImageList collection acts this way,
since it's a public collection on a class that may want to have complete
control over the actual images being displayed.

I haven't tested it, but I think it's entirely possible that the original
image references you've provided to the ImageList aren't in fact the ones
in use either. This would also ensure that you can't change the image
after the fact, that the control can internally do whatever caching is
required in order to ensure good performance or to otherwise ensure
consistent, correct display of the image.
One would think that a
collection of Images (which can be relatively large objects) would
default to
return a reference rather than returning a copy, especially when the
ImageList is part of System.Windows.Forms. (Meaning, hey this is a UI
collection, maybe this should be as fast as possible?).

Yes, you do want the control to be as fast as possible. Which is actually
one reason that the ImageList elements would be returned as cloned objects
rather than direct references. By ensuring that outside code can't modify
the image, the control can make some assumptions leading to performance
optimizations that otherwise wouldn't be possible.

Client code retrieval of the individual elements should be relatively
uncommon, while drawing the images as part of the control's display is
going to be very common. Which operation should be optimized, even if it
hinders the other? I'd say the latter.

As always, to some extent this is an arbitrary design choice, driven by
competing factors. Without knowing the specifics, it seems that the .NET
designer of this particular class decided that it was more important to
prevent modification of the images than to ensure efficiency. There are
other ways to get at the original references if you need to, though of
course you may wind up having to directly modify the ImageList collection
any time you want the displayed image in the control to change.

Pete
 
N

Nicholas Paldino [.NET/C# MVP]

Just to clarify, the ImageList is based on the image list control in the
common controls library for Windows, and to make storage of the images more
efficient, it actually stitches all the images into one big image, and
copies out the sections as needed. As per the first paragraph of the
documentation on Image Lists in the MSDN library:

http://msdn2.microsoft.com/en-us/library/bb761389(VS.85).aspx

So, when translating that into the .NET realm, it's going to use the
underlying implementation in the common controls library, and dole out
copies of the images as needed.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top