我有以下代码:
1 2 3 4 5 6 7 8 9 10 11
| MemoryStream foo (){
MemoryStream ms = new MemoryStream ();
// write stuff to ms
return ms ;
}
void bar (){
MemoryStream ms2 = foo ();
// do stuff with ms2
return;
} |
我分配的MemoryStream是否有可能以后无法以某种方式处置?
我有一个同行审查,坚持要求我手动关闭此链接,但我找不到信息来说明他是否有正确的观点。
-
询问您的审稿人他为什么认为您应该关闭它。 如果他谈论一般的良好实践,那么他可能很聪明。 如果他谈论提早释放记忆,那他错了。
您不会泄漏任何东西-至少在当前的实现中。
调用Dispose不会更快地清理MemoryStream占用的内存。呼叫之后,这将使您的流无法进行"读/写"呼叫,这可能对您有用或无效。
如果您绝对确定从不希望从MemoryStream转移到另一种流,则不调用Dispose不会对您造成任何伤害。但是,一般来说,这是一种很好的做法,部分原因是,如果您确实更改了使用其他Stream的方法,那么您就不想被难以发现的bug所困扰,因为您早早选择了简便的方法。 (另一方面,有YAGNI参数...)
无论如何,这样做的另一个原因是,新的实现可能会引入将在Dispose上释放的资源。
-
在这种情况下,该函数将返回MemoryStream,因为它提供了"可以根据调用参数而不同地解释的数据",因此它本可以是字节数组,但由于其他原因而更易于用作MemoryStream。因此,它绝对不会是另一个Stream类。
-
在那种情况下,Id仍会尝试按照一般原则来处理它-建立良好的习惯等-但如果它变得棘手,我就不会太担心。
-
如果真的很担心尽快释放资源,请在"使用"块之后立即将引用无效,这样就可以清除非托管资源(如果有),并且该对象可以进行垃圾回收。如果该方法立即返回,则可能不会有太大变化,但是如果您继续在该方法中执行其他操作(如请求更多内存),则肯定会有所作为。
-
@Triynko并非完全正确:有关详细信息,请参见:stackoverflow.com/questions/574019/。
-
@乔治。好吧,如果您使用using语句声明变量,则在块末尾都将其处置且超出范围,因此,空值将毫无意义(即,如果变量超出范围(或即将超出范围,或编译器确定)它无法再访问),则无需将其为null)。但是,我的意思是,由于超出范围之前的运行时条件而变得无用的类实例变量和局部变量,应设置为null,以确保可以尽快对其进行GC处理,尤其是在分配更多内存的情况下同时。
-
@triynko Raymond Chen最近在博客上发表了有关垃圾收集器的文章;基本上,要点是,一旦最后一个引用消失,就可以收集变量/类实例。您永远无法保证GC何时收集它,只是它实际上可以在范围离开当前方法之前发生。参见:blogs.msdn.com/b/oldnewthing/archive/2010/08/10/10048149.aspx
-
是的,那就是我说"或编译器确定它无法再访问"时的意思。多数民众赞成在"最后可能的使用"情况下,根据源代码,变量在超出范围之前可以被收集。下面有个有趣的帖子,乔纳森·艾伦(Jonathan Allen)写道,调用Dispose实际上可以延长变量的寿命,因为Dispose调用的存在表示对该对象的引用。我不知道编译器是否会意识到这样的延迟调用是没有意义的,还是可以确定是否可以更快地订购该调用。
-
YAGNI参数可以采用两种方式-因为决定不处置实现IDisposable的东西是一种特殊情况,与正常的最佳做法背道而驰,因此您可以辩称,在确实需要这样做之前,不应该这样做。 YAGNI原则。
-
@JonSkeet,只是想知道为什么MemoryStream甚至还要实现IDisposable?
-
@ johnny5:因为它继承了实现IDisposable的Stream。 (也有一些远程注意事项,但很少。)
-
@JonSkeet,这很有意义,谢谢,当您说远程事务时,是否表示另一台计算机正在分配流?
-
@ johnny5:不,我的意思是,如果您通过远程处理"发送"流,那么Dispose确实会产生影响。
-
感谢您的澄清
如果某些物品是一次性的,则应始终将其丢弃。您应该在bar()方法中使用using语句,以确保ms2被释放。
它最终将被垃圾收集器清除,但是调用Dispose始终是一个好习惯。如果在代码上运行FxCop,则会将其标记为警告。
-
因此,即使我可以使用" using"块,我仍然应该调用.Dispose()?
-
using块调用为您配置。
-
我必须不同意这个建议。通常Dispose只是一个空操作,调用它只会使您的代码混乱。
-
有关信息,在一些琐碎的测试(对于并行线程)中,FxCop和VSTS分析都没有发现琐碎的"使用"
-
@Grauenwolf:您的断言破坏了封装。作为消费者,您不必关心它是否为空操作:如果它是IDisposable的,则是Dispose()它的工作。
-
对于StreamWriter类而言,情况并非如此:仅当您处置StreamWriter时,才会处置连接的流-如果它被垃圾回收且其终结器被调用,它将永远不会处置该流-这是设计使然。
-
我知道这个问题来自2008年,但是今天我们有了.NET 4.0任务库。在大多数情况下,使用Tasks时不需要Dispose()。尽管我同意IDisposable的意思是"您最好在完成后处置此东西",但实际上并没有那么意思。
-
@Phil再过几年。文章仅讨论Task的情况,在大多数情况下,不需要手动处理。使用IO时,通常最好进行配置,因为它可以使行为更具确定性。 (例如,不处理FileStream可能会长时间保持锁定,从而阻塞了其他应用程序或部分代码。)
-
另一个不应丢弃IDisposable对象的示例是HttpClient aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong另一个来自BCL的示例,其中存在IDisposable对象,您不需要(甚至不应该)对其进行处置。这只是要记住,通常即使在BCL中,一般规则也有一些例外;)
-
Такимобразом,интерфейсIDisposable – MemoryStream`является没用吗?
-
fxCop现在已集成到VS中,所以请不要像我一样去使用Googling :)"分析->运行代码分析"以查看其运行情况。
-
注意:HttpClient是实现IDisposable的类的示例,但不应丢弃。尽管这个答案几乎总是正确的,但也有一些例外。
是的,存在泄漏,具体取决于您定义"泄漏"的方式以及之后的含义...
如果说泄漏是指"即使在使用完毕后,内存仍保持分配状态,无法使用",而后者是指在调用dispose之后的任何时间,那么可能是有泄漏的,尽管它不是永久的(例如应用程序运行时的寿命)。
要释放MemoryStream所使用的托管内存,您需要通过取消对它的引用来取消对其的引用,以便立即有资格进行垃圾回收。如果您未能做到这一点,那么您将在使用完之后创建一个临时泄漏,直到您的引用超出范围为止,因为在此期间内存将无法分配。
using语句(而不是简单地调用dispose)的好处是,您可以在using语句中声明引用。当using语句完成时,不仅会调用dispose,而且您的引用也会超出范围,从而有效地使引用无效,并使您的对象立即有资格进行垃圾回收,而无需您记住编写" reference = null"代码。
尽管不能立即取消引用某些内容并不是经典的"永久"内存泄漏,但绝对具有相同的效果。例如,如果您保留对MemoryStream的引用(即使在调用dispose之后),并且在方法中稍稍下移,则尝试分配更多的内存...仍被引用的内存流正在使用的内存将不可用直到您取消引用或引用超出范围为止,即使您调用了dispose并且已完成使用它。
-
我喜欢这个回应。有时人们忘记了使用的双重责任:渴望资源回收和渴望取消引用。
-
确实,尽管我听说Java不像Java,但是C#编译器检测到"最后可能的使用",因此,如果该变量注定要在其最后一次引用后超出范围,则它可能在最后一次使用后才有资格进行垃圾回收。在实际超出范围之前。参见stackoverflow.com/questions/680550/explicit-nulling
-
垃圾收集器和抖动不会以这种方式工作。范围是一种语言构造,不是运行时所要遵守的内容。实际上,您可以通过在块结束时添加对.Dispose()的调用来延长引用在内存中的时间。见ericlippert.com/2015/05/18/
这已经得到了回答,但是我只是补充说,信息隐藏的良好老式原则意味着您可能会在将来的某个时刻重构:
1 2 3 4 5 6
| MemoryStream foo ()
{
MemoryStream ms = new MemoryStream ();
// write stuff to ms
return ms ;
} |
至:
这强调了调用者不必关心返回的是哪种类型的Stream,并可以更改内部实现(例如,在进行单元测试的模拟时)。
如果您没有在bar实现中使用Dispose,则可能会遇到麻烦:
1 2 3 4 5 6 7 8
| void bar()
{
using (Stream s = foo())
{
// do stuff with s
return;
}
} |
不需要调用.Dispose()(或使用using换行)。
调用.Dispose()的原因是要尽快释放资源。
考虑一下,例如Stack Overflow服务器,那里的内存有限,有成千上万的请求进入。我们不想等待调度的垃圾回收,我们希望尽快释放该内存以便可用用于新的传入请求。
-
但是,在MemoryStream上调用Dispose不会释放任何内存。实际上,调用Dispose之后,您仍然可以在MemoryStream中获取数据-试试看:)
-
-1虽然对MemoryStream来说是正确的,但作为一般建议,这只是明显的错误。处置是释放非托管资源,例如文件句柄或数据库连接。内存不属于该类别。您几乎总是应该等待调度的垃圾回收以释放内存。
-
采用一种编码方式来分配和处置FileStream对象,而采用另一种编码方式来应对MemoryStream对象,会有什么好处呢?
-
FileStream包含非托管资源,这些资源实际上可以在调用Dispose时立即释放。另一方面,MemoryStream将托管字节数组存储在其_buffer变量中,该变量在处置时不会释放。实际上,在MemoryStreams Dispose方法中,_buffer甚至没有为空,这是一个SHAMEFUL BUG IMO,因为为空值引用可以使内存在处理时就适合GC。取而代之的是,缠绵的(但已放置)的MemoryStream引用仍然保留在内存中。因此,一旦处置它,如果它仍然在作用域内,则还应该将其清空。
-
@Triynko-"因此,一旦处置它,如果它仍在范围内,则也应该将其为空"-我不同意。如果在调用Dispose之后再次使用它,则将导致NullReferenceException。如果在"处置"之后未再次使用它,则无需将其清空; GC非常聪明。
所有流都实现IDisposable。在using语句中包装您的Memory流,您会很好并且很花哨。 using块将确保您的流无论如何都被关闭和处置。
无论您在哪里调用Foo,都可以使用(MemoryStream ms = foo())进行操作,我认为您应该还可以。
-
这个习惯我遇到的一个问题是,您必须确保没有在其他任何地方使用该流。例如,我创建了一个指向MemoryStream的JpegBitmapDecoder并返回了Frames [0](认为它会将数据复制到其自己的内部存储中),但是发现位图只会显示20%的时间-原来是因为我正在处理内存流。
-
如果您的内存流必须持续存在(即using块没有意义),则应调用Dispose并立即将变量设置为null。如果您将其丢弃,则不再需要使用它,因此也应立即将其设置为null。 chaiguy描述的内容听起来像是资源管理问题,因为除非您将交给您的东西承担处置的责任,并且提供参考的东西知道它不再对此负责,否则您不应提供对某个东西的引用。
我建议将MemoryStream包装在using语句的bar()中,主要是为了保持一致性:
-
目前,MemoryStream不会释放.Dispose()上的内存,但是有可能在将来的某个时候,或者您(或公司中的其他人)可能会用您自己的自定义MemoryStream代替它,等等。
-
这有助于在您的项目中建立模式,以确保所有流都得到处置-通过说"必须丢弃所有流"而不是"必须丢弃某些流,但不必丢弃某些流"来更明确地划清界限...
-
如果您更改了代码以允许返回其他类型的Stream,则无论如何都需要对其进行更改。
在创建和返回IDisposable时,在foo()之类的情况下,我通常要做的另一件事是确保构造对象与return之间的任何故障都被异常捕获,处置对象并重新抛出该异常:
1 2 3 4 5 6 7 8 9 10 11 12
| 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;
} |
您不会泄漏内存,但是您的代码审阅者是正确的,它指示您应该关闭流。这样做很有礼貌。
唯一可能导致内存泄漏的情况是,您不小心留下了对流的引用而从不关闭它。您仍然没有真正泄漏内存,但是您不必要地延长了声称使用内存的时间。
-
>您仍然没有真正泄漏内存,但是您不必要地延长了声称使用内存的时间。你确定吗?处理不释放内存,在函数中后期调用它实际上可能会延长无法收集的时间。
-
是的,乔纳森(Jonathan)有一点。实际上,在函数中延迟调用Dispose可能实际上会使编译器认为您需要在函数的最后访问流实例(以将其关闭)。这可能比根本不调用dispose更为糟糕(因此避免在函数中引用stream变量),因为否则编译器可以在函数的早期计算出最佳释放点(即"最后使用点")。 。
如果对象实现IDisposable,则必须在完成后调用.Dispose方法。
在某些对象中,Dispose的含义与Close相同,反之亦然,在这种情况下,两者都不错。
现在,对于您的特定问题,不,您不会泄漏内存。
-
"必须"是一个非常强烈的词。只要有规则,就有必要知道打破规则的后果。对于MemoryStream,几乎没有后果。
我不是.net专家,但也许这里的问题是资源,即文件句柄而不是内存。我猜垃圾回收器最终将释放流并关闭句柄,但是我认为最好将其显式关闭以确保将内容刷新到磁盘上。
-
MemoryStream都在内存中-这里没有文件句柄。
在垃圾收集语言中,非托管资源的处置是不确定的。即使您显式调用Dispose,也绝对无法控制何时真正释放后备内存。当对象超出范围时,将隐式调用Dispose,无论是通过退出using语句,还是从从属方法弹出调用堆栈。综上所述,有时对象实际上可能是托管资源(例如文件)的包装。这就是为什么最好在finally语句中明确关闭或使用using语句的原因。
干杯
-
不完全正确。退出using语句时调用Dispose。当对象刚超出范围时不调用Dispose。
MemorySteram就是字节数组,它是托管对象。
忘记处理或关闭它,除了最终确定的开销之外没有其他副作用。
只需在反射器中检查MemoryStream的构造函数或flush方法,就可以清楚地知道,除了遵循良好实践之外,您无需担心关闭或处置它。
-
-1:如果您打算发布4岁以上的问题且答案已被接受,请尝试使其变得有用。