【问题标题】:Disposing of object multiple times多次处理对象
【发布时间】:2012-06-25 15:34:53
【问题描述】:

我有以下代码,它使用流打开和修改 Open XML 文档,然后保存该流的新二进制表示:

MemoryStream stream = null;
try
{
    stream = new MemoryStream();
    stream.Write(this.GetBinaryRepresentation(), 0, this.GetBinaryRepresentation().Length);

    using (WordprocessingDocument document = WordprocessingDocument.Open(stream, true))
    {
        OfficeDocument.ModifyDocument(document);
        this.SetBinaryRepresentation(stream.ToArray());
        stream = null;
    }
}
finally
{
    if (stream != null)
    {
        stream.Dispose();
    }
}

我最初使用了两个 using 块(一个用于 MemoryStream,第二个用于 WordprocessingDocument),但收到警告 CA2202:“对象'流'可以在方法中多次处理...”根据MSDN article ,我将代码修改为上面的代码(将外部使用转换为尝试),但我仍然收到此警告。

我不确定如何构建此方法以确保 Dispose 在流中仅被调用一次。我不想简单地取消这个警告,因为 MSDN 文章指出你不应该依赖 Dispose 可以安全地多次调用。

【问题讨论】:

  • 只是一个评论:当你在 using 块中为空引用时,你将如何在流上调用Dispose
  • @BrianRasmussen - 考虑 stream.Write 抛出异常。在这种情况下,流未设置为 null 并被放置在 finally 块中。
  • @Henrik: 当然可以,但是在成功的情况下,我看不到Dispose 是如何被调用的。
  • 流由文档周围的 using 语句处理。当它超出范围时,将调用 document.Dispose(),它本身会处理任何底层资源,包括传入的流。
  • @JonSenchyna 啊,我不知道 WordprocessingDocument 类,所以我不知道它是否也处理流。感谢您的澄清。

标签: c# ca2202


【解决方案1】:

多次处理一个对象应该始终是安全的。来自documentation for Dispose

如果多次调用对象的 Dispose 方法,则对象必须忽略第一次调用之后的所有调用。如果多次调用其 Dispose 方法,则该对象不得抛出异常。

话虽如此,使用语句绝对是这里的方法。您收到该方法的唯一原因是,如果您明确处置对象,这不是必需的,因为 using 语句应始终仅处置对象一次。

【讨论】:

  • 谢谢,但根据代码分析规则的文档,您不应该依赖于:“不要禁止来自该规则的警告。即使已知对象的 Dispose可以安全地多次调用,未来的实现可能会发生变化。”
  • @Andrew 我认为这很荒谬,因为合同规定它应该始终是安全的。话虽如此,没有理由手动处理它 - 只需使用 using 语句即可。
  • 他们无法强制执行合同,因为 IDisposable 是一个接口,他们无法控制它的每个实现。我同意框架代码至少应该始终如此。
  • @JonSenchyna 问题是 - 任何不遵守合同的东西都是错误 - 简单明了。编写所有代码以避免第 3 方错误,而不是修复错误,这对 IMO 来说太过分了。我宁愿相信合同并报告违反合同的情况,也不愿担心避免理论问题
【解决方案2】:

MSDN 文章中的示例对您不起作用的原因是 他们在进入 using 块后立即将流设置为 null,而您在 using 块中使用流并在之后将流设置为空。如果在您的 stream = null 语句之前抛出异常,stream 将在 using 块退出时被处理,然后在您的 finally 块中再次处理。

不幸的是,由于您需要在 document 更新后访问您的流,我看不到一个干净的方法来使用他们在您的 using 语句中设置 stream = null 的示例以避免多个 Dispose() 调用。另一种方法是您可以在 try 块之外同时声明 streamdocument,然后在您的 finally 中清理它们,如下所示:

MemoryStream stream = null;
WordprocessingDocument document = null;
try
{
    stream = new MemoryStream();
    stream.Write(this.GetBinaryRepresentation(), 0, this.GetBinaryRepresentation().Length);

    document = WordprocessingDocument.Open(stream, true));

    OfficeDocument.ModifyDocument(document);
    this.SetBinaryRepresentation(stream.ToArray()); 
}
finally
{
    if( document != null)
    {
        document.Dispose();
    }
    // Catch the case where an error occurred before document was defined.
    else
    {
        stream.Dispose();
    }
}

【讨论】:

  • 我认为这行不通,OfficeDocument.ModifyDocument 方法正在更改流,并且根据 MemoryStream.ToArray() 文档 (msdn.microsoft.com/en-us/library/…) ToArray 返回数组的副本在那个时间点,因此对流后缀的更改不会反映在 binaryRepresentation 中。无论如何我确实尝试过,但单元测试失败 bc BinaryRepresentation 仍然包含原始文档。
  • 啊,你是对的。我没有仔细阅读您的 using 语句的内容。
  • 我更新了我的答案,以使用我对单个 try/finally 块的原始建议,而不是使用嵌套 using 块的 try/finally。这应该可以解决您的问题,而无需使用巧妙的引用操作。
【解决方案3】:

如果在将流设置为 null 之前在 using 块中引发异常,则流仍可能被释放两次。试试这个:

MemoryStream stream = null;
MemoryStream streamToDispose = null;
try
{
    streamToDispose = stream = new MemoryStream();
    stream.Write(this.GetBinaryRepresentation(), 0, this.GetBinaryRepresentation().Length);

    using (WordprocessingDocument document = WordprocessingDocument.Open(stream, true))
    {
        streamToDispose = null;
        OfficeDocument.ModifyDocument(document);
        this.SetBinaryRepresentation(stream.ToArray());
    }
}
finally
{
    if (streamToDispose != null)
    {
        streamToDispose.Dispose();
    }
}

【讨论】:

  • 您也可以简单地预先获取 stream.ToArray() ,然后在进入 using 块后立即将原始流设置为 null。设置对 null 的引用不会更改 yoru WordprocessingDocument 使用的基础对象的状态。
【解决方案4】:

using 语句处理对象 - 所以本质上你调用 dispose 两次

【讨论】:

  • 是的,我在想也许 WordProcessingDocument 正在为您包装处置,但也许不是......!
【解决方案5】:

当您的代码离开 WordProcessingDocument 周围的 using 块时,它将调用 dispose。

using (WordprocessingDocument document = WordprocessingDocument.Open(stream, true))

由于WordProcessingDocument 在其构造函数中采用stream 的实例,因此在调用WordProcessingDocument.Dispose 时,它将对该流实例调用dispose。然后您进入调用 stream.Dispose() 的 finally 块 - 您现在已经在流实例上调用了 Dispose() 两次。

【讨论】:

    猜你喜欢
    • 2014-04-10
    • 1970-01-01
    • 2019-04-29
    • 2011-04-28
    • 1970-01-01
    • 2017-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多