开发者

Getting Image by ResourceManager GetObject — Call it everytime or store the result?

Let's say that I have to show some graphics on some control. But there will be three images switched based on some condition. Three bitmap is added in the resource file.

So, I retrieve them by calling ResourceManager.GetObject.

The question is that, should it be:

  1. Everytime I have to switch image, I call GetObject to get it and assign to the control or
  2. hold the result of GetObject for each image at the start, so that there will only ever be 3 calls to the开发者_运维百科 GetObject. Assign image from my variables instead.

Doing 1) seems to produce a lot of GC Handle when viewed with CLR Profiler. Hoping to know any bad side effect of 2).

Thanks a lot.


Each call to GetObject will read the image from the assembly and load it into a Bitmap object.

Calling it many times will create significant overhead; you should store the images.


I have a WinForms application which uses many instances of the same Forms, each one with many images and icons for menus and buttons and such. All of these images are stored in the auto-generated [ProjectName].Properties.Resources class.

I noticed that the memory usage was terribly high; after only 10 or so Form instances it was using many hundreds of MBs of memory, and would cross 1+ GB easily after several more instances. I traced the issue to the ResourceManager.GetObject method. The GetObject method returns a new instance of every object requested, which just seemed wrong to me.

Instead of letting all those instances of images soak up memory only to fall out of scope, why not reuse them for future Form instances? So I created a custom CachedResourceMananger class and overrode the GetObject methods to return cached instances of the requested objects.

 /// <summary>
/// A custom Resource Manager that provides cached instances of objects.
/// This differs from the stock ResourceManager class which always
/// deserializes and creates new instances of every object.
/// After the first time an object is requested, it will be cached
/// for all future requests.
/// </summary>
public class CachedResourceManager : System.Resources.ResourceManager
{
    /// <summary>
    /// A hashtable is used to store the objects.
    /// </summary>
    private Hashtable objectCache = new Hashtable();

    public CachedResourceManager(Type resourceSource) : base(resourceSource)
    {
    }

    public CachedResourceManager(string baseName, Assembly assembly) : base(baseName, assembly)
    {
    }

    public CachedResourceManager(string baseName, Assembly assembly, Type usingResourceSet) : base(baseName, assembly, usingResourceSet)
    {
    }

    public CachedResourceManager() : base()
    {
    }

    /// <summary>
    /// Returns a cached instance of the specified resource.
    /// </summary>
    public override object GetObject(string name)
    {
        return GetObject(name, null);
    }

    /// <summary>
    /// Returns a cached instance of the specified resource.
    /// </summary>
    public override object GetObject(string name, CultureInfo culture)
    {
        // Try to get the specified object from the cache.
        var obj = objectCache[name];

        // If the object has not been cached, add it
        // and return a cached instance.
        if (obj == null)
        {
            objectCache[name] = base.GetObject(name, culture);
            obj = objectCache[name];
        }

        return obj;
    }
}

I then modified the resource manager property and field in the auto-generated [ProjectName].Properties.Resources class to use the custom resource manager, replacing global::System.Resources.ResourceManager with CachedResourceManager.

internal class Resources
{
    private static CachedResourceManager resourceMan;

    private static global::System.Globalization.CultureInfo resourceCulture;

    [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    internal Resources() {
    }

    /// <summary>
    ///   Returns the cached ResourceManager instance used by this class.
    /// </summary>
    [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
    internal static CachedResourceManager ResourceManager 
    {
        get {
               if (object.ReferenceEquals(resourceMan, null))
               {
                  CachedResourceManager temp = new CachedResourceManager("Project.Properties.Resources", typeof(Resources).Assembly);
                  resourceMan = temp;
               }
               return resourceMan;
            }
    }

    // Image/object properties for your resources

} // End of resources class

This reduced memory usage drastically and also greatly improved the loading times for new Form instances.


Just one other thing to point out about calling "ResourceManager.GetObject" each time you need to use a image from Resources is it seems to create a new Windows Handle each time. In your case probably not a big deal but if you were to hold on to them for a while like we did it might cause a issue.

We had a DataGridView that we were pushing images from Resources into different fields of the grid and when that grid got up over 3000 rows we were actually exceeding the maximum allowed Windows handles for a 32bit program.

The error appeared a random Argument Exceptions with message "Parameter is not valid". It took a few hours thinking we had a memory leak but finally found what we loaded this GUI with that grid the applications handles went from 700-1000 to over 10K before it even finished loading and would crash the whole program and could not recover. So I do recommend option 2 here.


I've also implemented the "read once then store in variable" concept in my classes.

To give an example, here is an excerpt from my code:

internal static class MyResourcesHolder
{
    private static Image _i1;
    private static Image _i2;
    private static Image _i3;
    private static Image _i4;
    private static Image _i5;

    public static Image MyImage01 => _i1 ?? (_i1 = Resources.MyImage01);
    public static Image MyImage02 => _i2 ?? (_i2 = Resources.MyImage02);
    public static Image MyImage03 => _i3 ?? (_i3 = Resources.MyImage03);
    public static Image MyImage04 => _i4 ?? (_i4 = Resources.MyImage04);
    public static Image MyImage05 => _i5 ?? (_i5 = Resources.MyImage05);
}

Maybe this helps someone someday.


The MSDN documentation states that the value of the resource is returned by ResourceManager.GetObject. Since it sounds like the individual bitmaps don't change at run-time, the only down-side I see to approach #2 is that your memory footprint will be a bit bigger.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜