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.