【问题标题】:Are ResourceStrings Really Cached?ResourceStrings 真的被缓存了吗?
【发布时间】:2013-04-10 14:09:11
【问题描述】:

所有,不久前(当我被吓倒的时候)我问了以下问题Performace Overheads when Using Resource Files (.resx) 关于使用资源字符串的性能开销。我得到了一个赞成的答案,并认为答案是正确的。然而,在此之前,我正在本地化在错误条件下调用的消息字符串,而不是性能关键 - 现在我被要求对我们的代码“强大”(许多性能关键代码、嵌入式循环等)实施本地化。

有一些时间来更详细地研究这个,我注意到调用像

这样的资源
Resources.MessageStrings.SomeResourceName

仅仅指调用自动生成的代码MessageStrings.Designer.cs,它使用

internal static string SomeResourceName {
    get {
        return ResourceManager.GetString("SomeResourceName", resourceCulture);}
}

所以深入挖掘,我想我会反编译 ResourceManager,它位于

C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.5\mscorlib.dll

查看GetString() 在做什么[它真的在缓存我的资源字符串吗?]。反编译,我发现

[__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;
  }
}

上面的代码中没有任何内容表明它正在“缓存”我的字符串(在这个词的典型/最真实的意义上),它似乎正在执行某种类型的复杂查找。我注意到该方法使用了未记录的 __DynamicallyInvokable 属性,并发现 Hans (What is the __DynamicallyInvokable attribute for?) 对此属性进行了简短的讨论。

我的问题是:对于性能关键代码,我是否可以依靠 ResourceManager 足够快(它会缓存我的字符串吗?),还是我需要预处理和自己缓存资源字符串?

感谢您的宝贵时间。

【问题讨论】:

    标签: c# winforms caching resources


    【解决方案1】:

    资源被缓存。如果您通过资源管理器跟踪调用堆栈,则如下所示: 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];
    }
    

    所以此时从哈希表中读取值。

    一旦访问资源文件,哈希表就会被填充:

    构造函数:

    [SecuritySafeCritical]
    public ResourceSet(string fileName)
    {
      this.Reader = (IResourceReader) new ResourceReader(fileName);
      this.CommonInit();
      this.ReadResources();
    }
    

    和 ReadResources:

    protected virtual void ReadResources()
    {
      IDictionaryEnumerator enumerator = this.Reader.GetEnumerator();
      while (enumerator.MoveNext())
      {
        object obj = enumerator.Value;
        this.Table.Add(enumerator.Key, obj);
      }
    }
    

    【讨论】:

    • @Killercam 很高兴我能帮上忙,首先也是在错误的轨道上 =)
    • @Killercam 那是我在my last comment to my answer here 中谈论的哈希表,但derape 已经发布了实际代码,这非常有帮助。 :)
    • @Killercam 您的代码不包含调用。它应该在您的代码在此行中调用的方法中调用:string @string = resourceSet1.GetString(name, this._ignoreCase);
    • 请注意,这不是必需的。我上面的代码使用resource.Designer.cs 中的自动生成代码。我上面显示的属性调用了您在答案中显示的代码...
    【解决方案2】:

    内存中保存的东西是它正在使用的ResourceSets,它们似乎直接保存了字符串。您不一定会在此方法中看到它,因为此时,每次调用都可能针对不同的资源名称和不同的文化。

    而且,和往常一样,这是您的分析告诉您存在性能问题的地方吗?尝试通过猜测可能效率低下的地方来优化代码几乎不是一个好主意。

    【讨论】:

    • +1 感谢您的宝贵时间。我同意这样一个事实,即猜测瓶颈将在哪里不是一个好主意。但是, 最好的做法是首先在可能的地方优化您的代码,首先减少关注领域的数量...
    猜你喜欢
    • 2017-06-28
    • 1970-01-01
    • 1970-01-01
    • 2010-11-20
    • 2019-03-02
    • 2019-08-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多