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.

Original source

Related problems