Are ResourceStrings Really Cached?
c#, caching, resources, winforms
Solution
The ressources are cached. If you follow the call stack through the resource manager it is like this: 1.
[System.Security.SecuritySafeCritical] // auto-generated
public virtual String GetString(String name, CultureInfo culture) {
//...
String value = rs.GetString(name, _ignoreCase);
//...
}
2.
public virtual string GetString(string name, bool ignoreCase)
{
object objectInternal = this.GetObjectInternal(name);
//...
}
3.
private object GetObjectInternal(string name)
{
//...
Hashtable hashtable = this.Table;
//...
return hashtable[(object) name];
}
So at this point the values is read from the hashtable.
The hashtable is filled once the ressource file is accessed:
Constructor:
[SecuritySafeCritical]
public ResourceSet(string fileName)
{
this.Reader = (IResourceReader) new ResourceReader(fileName);
this.CommonInit();
this.ReadResources();
}
and the ReadResources:
protected virtual void ReadResources()
{
IDictionaryEnumerator enumerator = this.Reader.GetEnumerator();
while (enumerator.MoveNext())
{
object obj = enumerator.Value;
this.Table.Add(enumerator.Key, obj);
}
}
Problem
All, a while back (when I was rushed off my feet) I asked the following question Performace Overheads when Using Resource Files (.resx) regarding the performance overhead of using resource strings. I got an up-voted answer and took the answer to be correct. However, before, I was localising message strings which are called in error conditions and not performance critical - now I have been asked to implement localisation to our codes 'power-house' (lots of performance critical code, embedded loops etc.). Having some time to look into this in more detail and I noticed that calling a resource like ``` Resources.MessageStrings.SomeResourceName ``` merely refers the call to the auto-generated code `MessageStrings.Designer.cs`, which uses ``` internal static string SomeResourceName { get { return ResourceManager.GetString("SomeResourceName", resourceCulture);} } ``` So digging deeper, I thought I would decompile `ResourceManager` which is found in C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.5\mscorlib.dll To see what `GetString()` was doing [Is it really caching my resource strings?]. Decompiling, I find ``` [__DynamicallyInvokable] public virtual string GetString(string name, CultureInfo culture) { if (name == null) throw new ArgumentNullException("name"); if (ResourceManager.s_IsAppXModel && object.ReferenceEquals((object) culture, (object) CultureInfo.CurrentUICulture)) culture = (CultureInfo) null; if (this._bUsingModernResourceManagement) { if (this._PRIonAppXInitialized) return this.GetStringFromPRI(name, culture == null ? (string) null : culture.Name, this._neutralResourcesCulture.Name); if (this._PRIExceptionInfo == null || this._PRIExceptionInfo._PackageSimpleName == null || this._PRIExceptionInfo._ResWFile == null) throw new MissingManifestResourceException(Environment.GetResourceString("MissingManifestResource_NoPRIresources")); throw new MissingManifestResourceException(Environment.GetResourceString("MissingManifestResource_ResWFileNotLoaded", (object) this._PRIExceptionInfo._ResWFile, (object) this._PRIExceptionInfo._PackageSimpleName)); } else { if (culture == null) culture = Thread.CurrentThread.GetCurrentUICultureNoAppX(); if (FrameworkEventSource.IsInitialized) FrameworkEventSource.Log.ResourceManagerLookupStarted(this.BaseNameField, this.MainAssembly, culture.Name); ResourceSet resourceSet1 = this.GetFirstResourceSet(culture); if (resourceSet1 != null) { string @string = resourceSet1.GetString(name, this._ignoreCase); if (@string != null) return @string; } foreach (CultureInfo culture1 in new ResourceFallbackManager(culture, this._neutralResourcesCulture, true)) { ResourceSet resourceSet2 = this.InternalGetResourceSet(culture1, true, true); if (resourceSet2 != null) { if (resourceSet2 != resourceSet1) { string @string = resourceSet2.GetString(name, this._ignoreCase); if (@string != null) { if (this._lastUsedResourceCache != null) { lock (this._lastUsedResourceCache) { this._lastUsedResourceCache.lastCultureName = culture1.Name; this._lastUsedResourceCache.lastResourceSet = resourceSet2; } } return @string; } else resourceSet1 = resourceSet2; } } else break; } if (FrameworkEventSource.IsInitialized) FrameworkEventSource.Log.ResourceManagerLookupFailed(this.BaseNameField, this.MainAssembly, culture.Name); return (string) null; } } ``` Nothing in the above code suggests that it is 'caching' my strings (in the typical/truest sense of the word), it seems it is doing a complex look-up of some type. I noticed that the method was using the undocumented `__DynamicallyInvokable` attribute, and found a breaf discussion of this attribute by Hans (What is the __DynamicallyInvokable attribute for?). My question is: For performance critical code can I rely on the `ResourceManager` being fast enough (does it cache my strings?), or do I need to pre-process and cache the resource strings myself? Thanks for your time.