【问题标题】:How the MemoryStream declared in outer using statement is still available after inner using statement closes?内部 using 语句关闭后,外部 using 语句中声明的 MemoryStream 如何仍然可用?
【发布时间】:2020-03-04 10:08:29
【问题描述】:

Microsoft 文档在此页面上有以下代码:

https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptostream?view=netframework-4.7

最内部的“使用”语句假设 Dispose csEncrypt,而后者又假设 Dispose msEncrypt 流。但是,在最内部的 using 语句范围之后,msEncrypt 仍然存在并被使用(调用了它的 ToArray())。

Microsoft 文档明确指出:“调用 StreamWriter.Dispose 时,StreamWriter 对象在提供的 Stream 对象上调用 Dispose()。”。后者意味着 csEncrypt 也被释放/关闭,这反过来又关闭了 msEncrypt (https://referencesource.microsoft.com/#mscorlib/system/security/cryptography/cryptostream.cs,23052627697efb77, Can a CryptoStream leave the base Stream open?)。

那么请解释一下我们如何仍然可以调用“msEncrypt.ToArray();”在最里面的 using 语句的范围结束之后?

using (MemoryStream msEncrypt = new MemoryStream())
{
    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
    {
        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
        {
            //Write all data to the stream.
            swEncrypt.Write(plainText);
        }

        encrypted = msEncrypt.ToArray();
    }
}

【问题讨论】:

标签: c# memorystream using-statement cryptostream


【解决方案1】:

请解释我们如何仍然可以调用“msEncrypt.ToArray();”在最里面的 using 语句的范围结束之后?

因为the documentation 向我们保证会是这样:

(在Stream对象的上下文中理解这一点很重要,the Close() and Dispose() methods are effectively synonymous.

更一般地说,重要的是要记住IDisposable.Dispose() 与实现该接口的对象的生命周期 没有任何关系。它所做的唯一一件事就是让您的代码在“完成使用它”时通知对象,以允许它清理(通常,释放非托管资源……对于任何托管对象,没有必要,因为 CLR 的垃圾收集器将照顾好那些)。

Dispose() 被调用时,任何对象实现都可以做任何认为合适的事情。虽然在调用Dispose() 之后对象会变得不可用是很常见的,但这不是必需的。事实上,有充分的理由允许MemoryStream 中的至少一些方法,如ToArray(),在对象被释放后仍然可用(但请注意,即使对于MemoryStream,大多数对象的成员处理后无法使用...ToArray() 是一个特例)。

在任何情况下,调用Dispose() 从不 将使对象引用本身无效。只要对象本身可访问,对象引用将始终保持有效。如果任何其他代码在其被处置后调用其成员之一,则由对象本身决定应该发生什么。大多数情况下,ObjectDisposedException 将被抛出,但在某些特定情况下,允许代码访问仅在代码几乎完成对象并且其主要目的已经实现时才主要有用的成员是有意义的。 MemoryStream.ToArray()就是这样的会员。

查看可能重复的问题:
Multiple using block, is this code safe?
Call to MemoryStream.GetBuffer() succeeds even after MemoryStream.Close(); Why?
Why can you still use a disposed object?
Clarify some things about IDisposable interface. Is instance (must be) equals null after calling Dispose?

另见密切相关的问题:
CA2202, how to solve this case

【讨论】:

    【解决方案2】:

    这些对象中的Dispose 方法会释放非托管资源,并且不会立即销毁整个对象。此外,当该对象不再使用时,垃圾收集器会自动释放分配给托管对象的内存,但由于您仍然在最里面的 using 块之外使用它,因此不会被垃圾收集然而。这就是您仍然可以调用ToArray 方法的原因,因为它不使用任何底层非托管资源并且该对象还没有被垃圾回收。此外,无法预测在不再使用对象后何时进行垃圾回收。

    如果多次调用对象的 Dispose 方法,则对象必须忽略第一次调用之后的所有调用。如果多次调用其 Dispose 方法,则该对象不得引发异常。 大多数人甚至建议取消此警告。更多信息here。 您的代码的重构版本(没有异常处理)可能是这样的:

    MemoryStream msEncrypt = new MemoryStream();
    
    CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
    
    StreamWriter swEncrypt = new StreamWriter(csEncrypt)
    
    //Write all data to the stream.
    swEncrypt.Write(plainText);
    encrypted = msEncrypt.ToArray();
    
    //Do more work if needed.
    //if you need to dispose of them manually and the current scope hasn't ended then just do:
    swEncrypt.Dispose();
    

    或者如果您想维护using 语句并添加异常处理,请遵循推荐的模式here

    【讨论】:

    • 那为什么编译器会给出警告 CA2202 说 csEncrypt 和 msEncrypt 可以被多次释放?
    • 编译器不会生成此类警告。这是 VS Code 分析。
    • 除了抑制它们之外,有没有办法重构这段代码来避免这些警告?
    • 我完全同意@Javier,但仍然感到困惑,如果最里面的 using 没有处理外部 using 声明,那么为什么它会为外部的声明多次调用 Dispose?
    • 警告说,“... 可以 ...”。我提供的链接就是这个。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-06
    • 2019-02-05
    • 1970-01-01
    • 2018-11-18
    • 1970-01-01
    • 2014-01-31
    相关资源
    最近更新 更多