在序列的元素上调用Dispose()
的最佳方法是什么?
假设有类似的东西:
IEnumerable<string> locations = ...
var streams = locations.Select ( a => new FileStream ( a , FileMode.Open ) );
var notEmptyStreams = streams.Where ( a => a.Length > 0 );
//from this point on only `notEmptyStreams` will be used/visible
var firstBytes = notEmptyStreams.Select ( a => a.ReadByte () );
var average = firstBytes.Average ();
如何在维护简洁代码的同时处理FileStream
个实例(一旦不再需要)
澄清一下:这不是一段实际的代码,这些行是一组类中的方法,而FileStream
类型也只是一个例子。
正在采取以下措施:
public static IEnumerable<TSource> Where<TSource> (
this IEnumerable<TSource> source ,
Func<TSource , bool> predicate
)
where TSource : IDisposable {
foreach ( var item in source ) {
if ( predicate ( item ) ) {
yield return item;
}
else {
item.Dispose ();
}
}
}
可能是个好主意?
或者:您是否总是在不尝试概括的情况下解决与IEnumerable<IDisposable>
有关的特定情况?这是因为拥有它是一种不典型的情况吗?你是在设计它的第一个地方吗?如果是这样,怎么样?
答案 0 :(得分:3)
我建议您将streams
变量转换为Array
或List
,因为第二次枚举(如果我没有记错的话)会创建新的副本流。
var streams = locations.Select(a => new FileStream(a, FileMode.Open)).ToList();
// dispose right away of those you won't need
foreach (FileStream stream in streams.Where(a => a.Length == 0))
stream.Dispose();
var notEmptyStreams = streams.Where(a => a.Length > 0);
// the rest of your code here
foreach (FileStream stream in notEmptyStreams)
stream.Dispose();
编辑对于这些限制,LINQ可能不是最好的工具。也许你可以通过一个简单的foreach
循环逃脱?
var streams = locations.Select(a => new FileStream(a, FileMode.Open));
int count = 0;
int sum = 0;
foreach (FileStream stream in streams) using (stream)
{
if (stream.Length == 0) continue;
count++;
sum += stream.ReadByte();
}
int average = sum / count;
答案 1 :(得分:3)
我会编写一个方法,比如AsDisposableCollection
,它返回一个包含IEnumerable
并且还会实现IDisposable
的方法,以便您可以使用通常的using
模式。这是一个更多的工作(方法的实现),但你只需要一次,然后你可以很好地使用该方法(根据你的需要):
using(var streams = locations.Select(a => new FileStream(a, FileMode.Open))
.AsDisposableCollection()) {
// ...
}
实现看起来大致如此(它不完整 - 只是为了表明这个想法):
class DisposableCollection<T> : IDisposable, IEnumerable<T>
where T : IDisposable {
IEnumerable<T> en; // Wrapped enumerable
List<T> garbage; // To keep generated objects
public DisposableCollection(IEnumerable<T> en) {
this.en = en;
this.garbage = new List<T>();
}
// Enumerates over all the elements and stores generated
// elements in a list of garbage (to be disposed)
public IEnumerator<T> GetEnumerator() {
foreach(var o in en) {
garbage.Add(o);
yield return o;
}
}
// Dispose all elements that were generated so far...
public Dispose() {
foreach(var o in garbage) o.Dispose();
}
}
答案 2 :(得分:2)
一个简单的解决方案如下:
List<Stream> streams = locations
.Select(a => new FileStream(a, FileMode.Open))
.ToList();
try
{
// Use the streams.
}
finally
{
foreach (IDisposable stream in streams)
stream.Dispose();
}
请注意,即使这样,如果其中一个FileStream构造函数在其他构造函数构造完成后仍然失败,理论上仍然无法关闭流。要解决这个问题,您需要更加小心地构建初始列表:
List<Stream> streams = new List<Stream>();
try
{
foreach (string location in locations)
{
streams.Add(new FileStream(location, FileMode.Open));
}
// Use the streams.
}
finally { /* same as before */ }
这是很多代码而且它并不像你想要的那样简洁,但是如果你想确保所有的流都被关闭,即使有异常,那么你应该这样做。
如果你想要更像LINQ的东西,你可能想阅读Marc Gravell的这篇文章:
答案 3 :(得分:0)
我已经找到了问题的一般解决方案:)
对我来说重要的事情之一就是一切都被正确处理,即使我不重复整个枚举,这是我使用像FirstOrDefault
这样的方法的情况(我经常这样做)
所以我想出了一个自定义Enumerator来处理所有的处理。你所要做的就是打电话给AsDisposeableEnumerable
,为你做所有的魔术。
GetMy.Disposeables()
.AsDisposeableEnumerable() // <-- all the magic is injected here
.Skip(5)
.where(i => i > 1024)
.Select(i => new {myNumber = i})
.FirstOrDefault()
请注意,这不适用于无限枚举。
我的自定义IEnumerable
public class DisposeableEnumerable<T> : IEnumerable<T> where T : System.IDisposable
{
private readonly IEnumerable<T> _enumerable;
public DisposeableEnumerable(IEnumerable<T> enumerable)
{
_enumerable = enumerable;
}
public IEnumerator<T> GetEnumerator()
{
return new DisposeableEnumerator<T>(_enumerable.GetEnumerator());
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
我的自定义IEnumerator
public class DisposeableEnumerator<T> : IEnumerator<T> where T : System.IDisposable
{
readonly List<T> toBeDisposed = new List<T>();
private readonly IEnumerator<T> _enumerator;
public DisposeableEnumerator(IEnumerator<T> enumerator)
{
_enumerator = enumerator;
}
public void Dispose()
{
// dispose the remaining disposeables
while (_enumerator.MoveNext()) {
T current = _enumerator.Current;
current.Dispose();
}
// dispose the provided disposeables
foreach (T disposeable in toBeDisposed) {
disposeable.Dispose();
}
// dispose the internal enumerator
_enumerator.Dispose();
}
public bool MoveNext()
{
bool result = _enumerator.MoveNext();
if (result) {
toBeDisposed.Add(_enumerator.Current);
}
return result;
}
public void Reset()
{
_enumerator.Reset();
}
public T Current
{
get
{
return _enumerator.Current;
}
}
object IEnumerator.Current
{
get { return Current; }
}
}
一种花哨的扩展方法,让事情看起来不错
public static class IDisposeableEnumerableExtensions
{
/// <summary>
/// Wraps the given IEnumarable into a DisposeableEnumerable which ensures that all the disposeables are disposed correctly
/// </summary>
/// <typeparam name="T">The IDisposeable type</typeparam>
/// <param name="enumerable">The enumerable to ensure disposing the elements of</param>
/// <returns></returns>
public static DisposeableEnumerable<T> AsDisposeableEnumerable<T>(this IEnumerable<T> enumerable) where T : System.IDisposable
{
return new DisposeableEnumerable<T>(enumerable);
}
}
答案 4 :(得分:0)
这是一个简单的包装器,允许您使用IEnumerable
处置任何using
(保留集合类型而不是转换为IEnumerable
,我们需要嵌套的通用参数类型{{ 3}}):
public static class DisposableEnumerableExtensions {
public static DisposableEnumerable<T> AsDisposable<T>(this IEnumerable<T> enumerable) where T : IDisposable {
return new DisposableEnumerable<T>(enumerable);
}
}
public class DisposableEnumerable<T> : IDisposable where T : IDisposable {
public IEnumerable<T> Enumerable { get; }
public DisposableEnumerable(IEnumerable<T> enumerable) {
this.Enumerable = enumerable;
}
public void Dispose() {
foreach (var o in this.Enumerable) o.Dispose();
}
}
用法:
using (var processes = System.Diagnostics.Process.GetProcesses().AsDisposable()) {
foreach (var p in processes.Enumerable) {
Console.Write(p.Id);
}
}
答案 5 :(得分:0)
使用https://lostechies.com/keithdahlby/2009/07/23/using-idisposables-with-linq/中的代码,您可以将查询转换为以下内容:
(
from location in locations
from stream in new FileStream(location, FileMode.Open).Use()
where stream.Length > 0
select stream.ReadByte()).Average()
您需要以下扩展方法:
public static IEnumerable<T> Use<T>(this T obj) where T : IDisposable
{
try
{
yield return obj;
}
finally
{
if (obj != null)
obj.Dispose();
}
}
这将妥善处理您创建的所有流,无论它们是否为空。