你如何使用LINQ处理IDisposable序列?

时间:2010-09-11 23:12:05

标签: c# linq ienumerable dispose idisposable

在序列的元素上调用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>有关的特定情况?这是因为拥有它是一种不典型的情况吗?你是在设计它的第一个地方吗?如果是这样,怎么样?

6 个答案:

答案 0 :(得分:3)

我建议您将streams变量转换为ArrayList,因为第二次枚举(如果我没有记错的话)会创建新的副本流。

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()

请注意,这不适用于无限枚举。

代码

  1. 我的自定义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();
        }
    }
    
  2. 我的自定义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; }
        }
    }
    
  3. 一种花哨的扩展方法,让事情看起来不错

    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();
    }
}

这将妥善处理您创建的所有流,无论它们是否为空。