如果未关闭.NET中的MemoryStream,是否会造成内存泄漏?


111

我有以下代码:

MemoryStream foo(){
    MemoryStream ms = new MemoryStream();
    // write stuff to ms
    return ms;
}

void bar(){
    MemoryStream ms2 = foo();
    // do stuff with ms2
    return;
}

我分配的MemoryStream是否有可能以后无法以某种方式处置?

我有一个同行审查,坚持要求我手动关闭此链接,但我找不到信息来说明他是否有正确的论点。


41
询问您的审稿人为什么他认为您应该关闭它。如果他谈论一般的良好实践,那么他可能很聪明。如果他谈论提早释放记忆,那是错误的。
乔恩·斯基特

Answers:


60

如果某些物品是一次性的,则应始终将其丢弃。您应该using在您的bar()方法中使用一个语句以确保ms2获取Dispose。

最终它会被垃圾收集器清除,但是调用Dispose始终是一个好习惯。如果在代码上运行FxCop,则会将其标记为警告。


16
using块调用为您配置。
尼克,

20
@Grauenwolf:您的断言破坏了封装。作为消费者,您不必关心它是否为空操作:如果它是IDisposable的,那么它就是Dispose()它的工作。
马克·格雷韦尔

4
对于StreamWriter类而言,情况并非如此:当您处置StreamWriter时,才会处置连接的流-如果它被垃圾回收且其终结器被调用,它将永远不会处置该流–这是设计使然。
2011年

4
我知道这个问题来自2008年,但是今天我们有了.NET 4.0任务库。在大多数情况下,使用Tasks时不需要 Dispose()。虽然我会同意,IDisposable的应该是说“你的这更好的处置当你完成,”它并没有真正意味了。
Phil

7
另一个不应丢弃IDisposable对象的示例是HttpClient aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong这是BCL中的另一个示例,其中存在IDisposable对象,您不需要(甚至不应该)处置它。这只是要记住,通常即使在BCL中,一般规则也有一些例外;)
Mariusz Pawelski

166

您不会泄漏任何东西-至少在当前实施中。

调用Dispose不会更快地清理MemoryStream占用的内存。呼叫之后,这将使您的流无法进行读/写呼叫,这对您可能有用也可能没有用。

如果您绝对确定从不希望从MemoryStream转移到另一种流,那么不调用Dispose不会对您造成任何伤害。但是,一般来说,这是一种很好的做法,部分原因是,如果您确实更改了使用其他Stream的方法,那么您就不想被难以发现的bug所困扰,因为您早早选择了简便的方法。(另一方面,有YAGNI参数...)

无论如何,这样做的另一个原因是新的实现可能会引入将在Dispose上释放的资源。


在这种情况下,该函数将返回MemoryStream,因为它提供了“可以根据调用参数而不同地解释的数据”,因此它本可以是字节数组,但由于其他原因而更易于用作MemoryStream。因此,它绝对不会是另一个Stream类。
编码员

在那种情况下,我仍然会尝试按照一般原则来处理它-建立良好的习惯等-但是如果它变得棘手,我不会太担心。
乔恩·斯基特

1
如果真的很担心尽快释放资源,请在“使用”块之后立即将引用取消为空,以便清除非托管资源(如果有),并且该对象可以进行垃圾回收。如果该方法立即返回,则可能不会有太大变化,但是如果您继续在该方法中执行其他操作(例如请求更多内存),则肯定会有所作为。
Triynko 2010年

@Triynko并非完全正确:有关详细信息,请参见:stackoverflow.com/questions/574019/…
乔治·斯托克

10
YAGNI的论点可以用两种方式来理解-因为决定不处置实现的东西IDisposable是一种特殊情况,与正常的最佳实践背道而驰,因此您可能会争辩说,在YAGNI的指导下,直到真正需要时才应该这样做原理。
乔恩·汉纳

26

是有一个泄漏,这取决于你如何定义泄漏,多少后,你的意思是......

如果说泄漏是指“即使在使用完毕后,内存仍保持分配状态,无法使用”,而后者是指在调用dispose之后的任何时间,那么可能是有泄漏的,尽管它不是永久的(例如应用程序运行时的寿命)。

要释放MemoryStream所使用的托管内存,您需要通过取消对它的引用来取消对其的引用,以使其立即有资格进行垃圾回收。如果您这样做失败,那么从使用完毕开始,您将创建一个临时泄漏,直到您的引用超出范围为止,因为与此同时,该内存将无法分配。

using语句(而不是简单地调用dispose)的好处是,您可以在using语句中声明引用。using语句完成时,不仅会调用dispose,而且您的引用也会超出范围,从而有效地使引用无效,并使您的对象立即有资格进行垃圾回收,而无需您记住编写“ reference = null”代码。

虽然不能立即取消引用某些内容并不是经典的“永久”内存泄漏,但肯定具有相同的效果。例如,如果保留对MemoryStream的引用(即使在调用dispose之后),并且在方法中稍稍下移,则尝试分配更多的内存...仍被引用的内存流正在使用的内存将不可用直到您取消引用或引用超出范围为止,即使您调用了dispose并且已完成使用它。


6
我喜欢这个回应。有时人们会忘记使用的双重责任:渴望资源回收渴望取消引用。
套件

1
确实,尽管我听说不同于Java,但C#编译器检测到“最后可能的使用”,因此,如果该变量注定要在其最后一次引用后超出范围,则它可能在最后一次可能使用后才有资格进行垃圾回收。在实际超出范围之前。参见stackoverflow.com/questions/680550/explicit-nulling
Triynko

2
垃圾收集器和抖动不会那样工作。范围是一种语言构造,不是运行时将遵循的语言。实际上,您可以通过在块结束时添加对.Dispose()的调用来延长引用在内存中的时间。看到ericlippert.com/2015/05/18/…–
Pablo Montilla

8

无需调用.Dispose()(或使用来包装Using)。

您打电话的原因.Dispose()是尽快释放资源

考虑一下,例如Stack Overflow服务器,那里的内存有限,有成千上万的请求进入。我们不想等待调度的垃圾回收,我们希望尽快释放该内存以便可用用于新的传入请求。


24
但是,在MemoryStream上调用Dispose不会释放任何内存。实际上,调用Dispose之后,您仍然可以在MemoryStream中获取数据-试试看:)
Jon Skeet

12
-1虽然对于MemoryStream而言确实如此,但作为一般建议,这完全是错误的。处置是释放非托管资源,例如文件句柄或数据库连接。内存不属于该类别。您几乎总是应该等待调度的垃圾回收以释放内存。

1
采用一种编码方式分配和处理FileStream对象而采用另一种编码方式对对象有什么好处MemoryStream呢?
罗伯特·罗斯尼

3
FileStream包含非托管资源,这些资源实际上可以在调用Dispose时立即释放。另一方面,MemoryStream将托管字节数组存储在其_buffer变量中,该变量在处置时不会释放。实际上,在MemoryStream的Dispose方法中,_buffer甚至没有为空,这是一个SHAMEFUL BUG IMO,因为为空引用可以使内存在处理时就符合GC的条件。取而代之的是,缠绵的(但已放置)的MemoryStream引用仍然保留在内存中。因此,一旦处置它,如果它仍然在作用域内,则还应该将其清空。
Triynko 2010年

@Triynko-“因此,一旦处置它,如果它仍在范围内,则也应该将其为空”-我不同意。如果在调用Dispose之后再次使用它,则将导致NullReferenceException。如果在Dispose之后不再使用它,则无需将其清空。GC足够聪明。
2014年

8

这已经得到了回答,但是我只是补充说,信息隐藏的良好老式原则意味着您可能在将来的某个时刻想要重构:

MemoryStream foo()
{    
    MemoryStream ms = new MemoryStream();    
    // write stuff to ms    
    return ms;
}

至:

Stream foo()
{    
   ...
}

这强调了调用者不必关心返回的是哪种Stream,并可以更改内部实现(例如,在进行单元测试的模拟时)。

如果您没有在bar实现中使用Dispose,则可能会遇到麻烦:

void bar()
{    
    using (Stream s = foo())
    {
        // do stuff with s
        return;
    }
}

5

所有流都实现IDisposable。在using语句中包装您的Memory流,您会很好并且很花哨。using块将确保您的流无论如何都被关闭和处置。

无论您在哪里调用Foo,都可以使用(MemoryStream ms = foo())进行操作,我认为您应该还可以。


1
我已经习惯了这种习惯的一个问题是,您必须确保没有在其他任何地方使用该流。例如,我创建了一个指向MemoryStream的JpegBitmapDecoder并返回了Frames [0](认为它会将数据复制到其自己的内部存储中),但是发现位图只会显示20%的时间-原来是因为我正在处理内存流。
devios1 2010年

如果您的内存流必须持久(即using块没有意义),则应调用Dispose并将变量立即设置为null。如果您将其丢弃,则不再需要使用它,因此也应立即将其设置为null。chaiguy所描述的内容听起来像是资源管理问题,因为除非您要交出的东西要承担处置的责任,并且交出参考的东西知道它不再负责,否则您不应交出对某物的参考。这样做。
Triynko 2010年

2

您不会泄漏内存,但是您的代码审阅者是正确的,它指示您应该关闭流。这样做是有礼貌的。

可能会泄漏内存的唯一情况是,您不小心留下了对流的引用,并且从不关闭它。您还没有真正泄漏内存,但你你声称要使用它不必要延长的时间量。


1
>您仍然没有真正泄漏内存,但是您不必要地延长了声称使用内存的时间。你确定吗?Dispose不会释放内存,而在函数中后期调用它实际上会延长无法收集的时间。
乔纳森·艾伦

2
是的,乔纳森(Jonathan)有一点。实际上,在函数中延迟调用Dispose可能实际上会使编译器认为您需要在函数的后期访问流实例(以将其关闭)。这可能比根本不调用dispose更为糟糕(因此避免在函数中引用stream变量),因为否则编译器可以在函数的早期计算出最佳释放点(即“最后使用点”)。 。
Triynko 2010年

2

我建议将MemoryStream包装bar()using声明中主要是为了保持一致性:

  • 现在,MemoryStream不会释放内存 .Dispose(),但有可能在将来的某个时候,或者您(或公司中的其他人)可能会用您自己的自定义MemoryStream替换它,等等。
  • 这有助于在您的项目中建立模式,以确保所有流都得到处置-通过说“必须丢弃所有流”而不是“必须丢弃某些流,但不必丢弃某些流”来更明确地划清界线...
  • 如果您更改了代码以允许返回其他类型的Stream,则无论如何都需要对其进行更改。

foo()在创建和返回IDisposable之类的情况下,我通常要做的另一件事是确保在构造对象与对象之间的任何故障return都被异常捕获,处置对象并重新抛出异常:

MemoryStream x = new MemoryStream();
try
{
    // ... other code goes here ...
    return x;
}
catch
{
    // "other code" failed, dispose the stream before throwing out the Exception
    x.Dispose();
    throw;
}

1

如果对象实现IDisposable,则必须在完成后调用.Dispose方法。

在某些对象中,Dispose的含义与Close相同,反之亦然,在这种情况下,两者都不错。

现在,对于您的特定问题,不,您不会泄漏内存。


3
“必须”是一个很强的词。只要有规则,就有必要了解打破规则的后果。对于MemoryStream,几乎没有后果。
乔恩·斯基特

-1

我不是.net专家,但也许这里的问题是资源,即文件句柄而不是内存。我猜垃圾回收器最终将释放流并关闭句柄,但是我认为最好将其显式关闭,以确保将内容刷新到磁盘上。


MemoryStream都在内存中-这里没有文件句柄。
乔恩·斯基特

-2

用垃圾收集语言处理非托管资源是不确定的。即使您显式调用Dispose,也绝对无法控制何时真正释放后备内存。当对象超出范围时(无论是通过退出using语句还是从从属方法弹出调用栈),都将隐式调用Dispose。综上所述,有时对象实际上可能是托管资源(例如文件)的包装。这就是为什么最好在finally语句中明确关闭或使用using语句的原因。干杯


1
不完全正确。退出using语句时调用Dispose。当对象刚超出范围时不调用Dispose。
亚历山大·阿布拉莫夫

-3

MemorySteram就是字节数组,它是托管对象。忘记处理或关闭它,除了最终确定的开销之外没有其他副作用。
只需在反射器中检查MemoryStream的构造函数或flush方法,就很清楚为什么除了遵循良好实践之外,您不必担心关闭或处置它。


6
-1:如果您要发布4岁以上且答案已被接受的问题,请尝试使其变得有用。
Tieson T.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.