【问题标题】:MemoryStream instead of byte[] for files added as resources?MemoryStream 而不是 byte[] 作为资源添加的文件?
【发布时间】:2012-05-15 13:52:12
【问题描述】:

我有一个 .NET 程序集,我向其中添加了许多文件作为资源(二进制文件,每个文件 >500KB)。我以前一直在使用自动生成的Resources 类上的ResourceManager.GetObject() 方法访问这些,它返回一个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

【问题讨论】:

    标签: .net visual-studio-2010 stream resx


    【解决方案1】:

    您可以在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) 返回一个UnmanagedMemoryStream,它实际上在内部调用GetObject(String),并且仍然创建了一个副本。如果您调试您的应用程序并分析内存,您将看到内存仍在分配中。

    我尝试了多种方法来解决这个问题,方法是在 unsafe 上下文中使用指针,然后进行反射,但没有任何效果。 ResourceManager 没有那么灵活或优化。

    不过,我能够找到解决方案,但它需要您使用 Embedded Resources。除了将资源文件的构建操作设置为Embedded Resource 以获取Build 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 找到解决方案,但它不够灵活。

    编辑我写了in-depth blog article here about this subject

    【讨论】:

    • 我担心,使用您的方法,整个 byte[] 将在构造和返回流之前被读入内存。这就是为什么我希望能够调用GetStream 而不是GetObject;避免先将整个文件读入内存。
    • 我以为我拥有它,但我需要再努力一点。当我有适合你的东西时,我会更新。
    • 更新了我的答案。简而言之,您无法避免使用 ResourceManager 的足迹。
    • 感谢您为探索问题和测试解决方案所付出的努力。考虑到这一点,我可能会改用嵌入式资源。
    • 很高兴我能帮上忙,这将变成博客材料,我将在其中进行更深入的研究,但我很高兴找到解决方案。希望你一切顺利。
    猜你喜欢
    • 2013-12-17
    • 1970-01-01
    • 2012-12-20
    • 2023-04-06
    • 2012-04-29
    • 2011-08-07
    • 1970-01-01
    • 2011-12-13
    • 2011-02-05
    相关资源
    最近更新 更多