10

我有一个 .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为我设置类型?

4

1 回答 1

7

您可以在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它时,它会立即通过资源的大小增加工作集。

You will probably need to swap out the call to EndsWith that checks for the proper resource name in the manifest, because the names of the resources are slightly different than accessing it directly through the ResourceManager.

I am actually disappointed that I was not able to find a solution using the existing ResourceManager, but it just isn't flexible enough.

Edit I wrote an in-depth blog article here about this subject.

于 2012-05-07T01:20:46.520 回答