即使在MemoryStream.Close()之后,对MemoryStream.GetBuffer()的调用也会成功;为什么?

时间:2014-05-26 07:56:26

标签: c# garbage-collection dispose memorystream

我在一些开源代码中找到了以下构造:

var mstream = new MemoryStream();
// ... write some data to mstream
mstream.Close();
byte[] b = mstream.GetBuffer();

我认为这段代码会出现"意外"行为并且可能抛出异常,因为根据MSDN documentation,对Close的调用应该实际上是对Dispose的调用。

然而,就我从试验中所知,对GetBuffer()的调用总是成功并返回有效结果,即使我Thread.Sleep持续20秒或通过{强制执行垃圾收集{1}}。

即使GC.Collect() / GetBuffer()之后<{1}}的来电也应该成功?在这种情况下,为什么不在Close处置中释放底层缓冲区?

4 个答案:

答案 0 :(得分:4)

从技术上讲,MemoryStream没有任何东西要处理。字面上没什么,它没有操作系统句柄,非托管资源,什么都没有。它只是byte[]的包装器。您可以做的就是将buffer(内部数组)设置为null,BCL团队由于某种原因没有这样做。

正如@mike在评论中指出的那样,BCL团队希望GetBufferToArray能够工作,即使在处理后,我们也不确定为什么? Reference source

以下是Dispose的实施方式。

protected override void Dispose(bool disposing)
{
    try {
        if (disposing) {
            _isOpen = false;
            _writable = false;
            _expandable = false;
            // Don't set buffer to null - allow GetBuffer & ToArray to work.
    #if FEATURE_ASYNC_IO
                        _lastReadTask = null;
    #endif
        }
    }
    finally {
        // Call base.Close() to cleanup async IO resources
        base.Dispose(disposing);
    }
}

GetBuffer位于

之下
public virtual byte[] GetBuffer()
{
    if (!this._exposable)
    {
        throw new UnauthorizedAccessException(Environment.GetResourceString("UnauthorizedAccess_MemStreamBuffer"));
    }
    return this._buffer;
}

正如您在Dispose _buffer中所看到的那样,未触及,GetBuffer没有处理过的检查。

答案 1 :(得分:4)

  1. 不需要。缓冲区是托管内存,因此正常的垃圾收集将处理它,而不需要包含在处理中。
  2. 能够获取内存流的字节是有用的,即使在流已经关闭之后(在将蒸汽传递给将某些内容写入流然后关闭所述流的方法之后可能会自动发生) 。为了实现这一目标,该对象需要保留缓冲区以及已写入其中的记录。
  3. 在考虑第二点时,在很多情况下调用ToArray()实际上更有意义(如上所述,需要GetBuffer()返回的内存存储仍然存在)< strong>在之后关闭了流,因为关闭了流可以保证进一步尝试写入流将失败。因此,如果您有一个过早获取数组的错误,它将抛出一个异常,而不仅仅是给您不正确的数据。 (显然,如果您明确希望通过流操作部分获取当前数组,则这是另一回事)。它还保证所有流都完全刷新,而不是将其部分数据放在临时缓冲区中(MemoryStream未缓冲,因为MemoryStream本质上 是缓冲区,但是你可能已经将它与链式流或具有各自缓冲区的编写器一起使用。)

答案 2 :(得分:1)

由于GC是非确定性的,因此您无法强制它立即处置MemoryStream,因此实例不会被标记为立即处置,而是将其标记为处置。这意味着有一段时间,直到它真正处理完毕,您可以使用它的一些功能。因为它可以很好地引用它的缓冲区,所以这就是GetBuffer方法的样子:

public virtual byte[] GetBuffer()
{
    if (!this._exposable)
    {
        throw new UnauthorizedAccessException(Environment.GetResourceString("UnauthorizedAccess_MemStreamBuffer"));
    }
    return this._buffer;
}

答案 3 :(得分:1)

与大多数接口方法不同,IDisposable.Dispose不承诺做任何事情。相反,它提供了一种标准方法,通过该方法,对象的所有者可以让该对象知道不再需要其服务,以防对象可能需要使用该信息。如果某个对象已经要求外部实体代表它执行某些操作,并且已经向那些外部实体承诺,当 他们的服务不再需要时,他们知道它们 {1}}方法可以将通知转发给这些实体。

如果一个对象有一个方法只能在对象有代表它的外部实体时执行,那么在这些实体被解除后尝试调用该方法应该抛出Dispose 而不是以其他方式失败。此外,如果有一种方法在实体被解雇后不可能有用,即使某个特定实际上不需要使用该实体,它也应该经常抛出ObjectDisposedException。另一方面,如果一个特定的呼叫在一个对象解雇了代表它的所有实体之后具有合理的意义,那么为什么不应该允许这样的呼叫成功也没有特别的理由。

我会查看ObjectDisposedException,就像我查看ObjectDisposedException的集合修改InvalidOperationException一样:如果某些情况(IEnumerator<T>.MoveNext()或者集合的修改) )会阻止方法表现&#34;通常&#34;,允许该方法抛出指示的异常,并且不允许以某种其他错误方式运行。另一方面,如果该方法能够毫无困难地实现其目标,并且如果这样做有意义,则应该认为这种行为与抛出异常一样可接受。一般而言,物体不需要在这种不利条件下操作,但有时它们可​​能对它们有帮助[例如,集合的更改不会使Dispose的枚举失效,因为这样的失效会使并发枚举无效]。