У меня есть приложение WinForms, в котором используется множество экземпляров одних и тех же форм, в каждом из которых много изображений и значков для меню, кнопок и тому подобного. Все эти изображения хранятся в автоматически сгенерированном классе [ProjectName].Properties.Resources
.
Я заметил, что использование памяти было ужасно высоким; только после примерно 10 экземпляров Form он использовал сотни сотен мегабайт памяти и легко пересекал бы 1+ Гбайт еще через несколько экземпляров. Я проследил проблему до ResourceManager.GetObject
метода. Метод GetObject
возвращает новый экземпляр каждого запрошенного объекта, что мне показалось неправильным.
Вместо того, чтобы все эти экземпляры изображений впитывали память только для того, чтобы выпасть из области видимости, почему бы не использовать их для будущих экземпляров Form? Поэтому я создал собственный класс CachedResourceMananger
и переопределил методы GetObject
для возврата кэшированных экземпляров запрошенных объектов.
/// <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;
}
}
Затем я изменил свойство и поле менеджера ресурсов в автоматически сгенерированном классе [ProjectName].Properties.Resources
, чтобы использовать собственный менеджер ресурсов, заменив global::System.Resources.ResourceManager
на 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
Это значительно сократило использование памяти, а также значительно улучшило время загрузки для новых экземпляров форм.