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.