Dot-Net

MemoryStream 而不是字節對於作為資源添加的文件?

  • September 13, 2016

我有一個 .NET 程序集,我向其中添加了許多文件作為資源(二進製文件,每個文件 >500KB)。我以前一直在使用ResourceManager.GetObject()自動生成的Resources類上的方法訪問這些,它返回一個byte[].

出於性能和語法的原因,我寧願將這些二進制資源作為流而不是字節數組進行操作。我發現,通過手動編輯 .resx 文件並將<value>元素中的類名稱從System.Byte[]更改為System.IO.MemoryStream,我可以使用該ResourceManager.GetStream()方法成功地以流的形式訪問資源,例如

<data name="MyFile" type="System.Resources.ResXFileRef, System.Windows.Forms">
   <value>..\Resources\MyFile.ext;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>

變成:

<data name="MyFile" type="System.Resources.ResXFileRef, System.Windows.Forms">
   <value>..\Resources\MyFile.ext;System.IO.MemoryStream, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>

這種方法的唯一缺點是 Visual Studio 總是在byte[]表單中添加新的文件資源。有沒有辦法讓它MemoryStream為我設置類型?

您可以在ResourceManager.

public static class ResourceExtensions
{
   public static MemoryStream GetMemoryStream(this ResourceManager resourceManager, String name) {
       object resource = resourceManager.GetObject(name);

       if (resource is byte[]) {
           return new MemoryStream((byte[])resource);
       }
       else {
           throw new System.InvalidCastException("The specified resource is not a binary resource.");
       }
   }
}

打電話

ResourceManager resourceManager = Properties.Resources.ResourceManager;
MemoryStream stream = resourceManager.GetMemoryStream("binaryResource");

不過,這似乎也一樣。

MemoryStream stream = new MemoryStream(Properties.Resources.SomeBinaryResource);

我不確定我是否會修改資源文件,因為它們很容易更改,而且我確信在某些情況下 Visual Studio 會覆蓋更改。

根據您的擔憂,問題在於這會將數據的副本創建到記憶體中,從而產生記憶體佔用。對於壽命很短的輕量級資源,這不是問題,但它可能是一個大問題。

答案很簡短:您無法避免使用ResourceManager. 問題是兩者都ResourceManager.GetObject(String)創建ResourceManager.GetStream(String)了數據的副本。即使GetStream(String)返回 an UnmanagedMemoryStream,它實際上也會在GetObject(String)內部呼叫,並且仍然會創建一個副本。如果您調試應用程序並分析記憶體,您將看到記憶體仍然被分配。

我嘗試了多種方法來解決這個問題,方法是在unsafe上下文中使用指針和反射,但沒有任何效果。ResourceManager只是沒有那麼靈活或優化。

但是,我能夠找到解決方案,但它需要您使用Embedded Resources. 除了將資源文件的建構操作設置Embedded ResourceBuild Action. 使用它,您可以使用反射來創建一個UnmanagedMemoryStream不創建數據副本的對象。

private UnmanagedMemoryStream GetUnmanagedMemoryStream(String embeddedResourceName) {
   Assembly assembly = Assembly.GetExecutingAssembly();

   string[] resourceNames = assembly.GetManifestResourceNames();
   string resourceName = resourceNames.SingleOrDefault(resource => resource.EndsWith(embeddedResourceName, StringComparison.InvariantCultureIgnoreCase));

   if (resourceName != null) {
       return (UnmanagedMemoryStream)assembly.GetManifestResourceStream(resourceName);
   }
   else {
       throw new System.ArgumentException("The specified embedded resource could not be found.", "embeddedResourceName");
   }
}

我沒有對此進行廣泛的測試,但它確實有效。我的測試數據是一個 17 兆字節的小文件。我的測試應用程序的工作集記憶體從大約 50 兆字節開始,在將資源檢索到流中後,它不會改變。當使用ResourceManager它時,它會立即通過資源的大小增加工作集。

您可能需要換出對EndsWith清單中正確資源名稱檢查的呼叫,因為資源名稱與直接通過ResourceManager.

我實際上很失望,我無法使用現有的 . 找到解決方案ResourceManager,但它不夠靈活。

編輯在這裡寫了一篇關於這個主題的深入部落格文章

引用自:https://stackoverflow.com/questions/10475593