Как вы справляетесь с последовательностями IDisposable с использованием LINQ? - PullRequest
3 голосов
/ 12 сентября 2010

Каков наилучший подход для вызова 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 ]

3 голосов
/ 12 сентября 2010

Я бы написал метод, скажем, 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();
  }
}
3 голосов
/ 12 сентября 2010

Я предлагаю вам превратить переменную 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;
2 голосов
/ 12 сентября 2010

Простое решение заключается в следующем:

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, вам может понадобиться прочитать эту статью Марка Гравелла:

0 голосов
/ 12 ноября 2015

Используя код из 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();
    }
}

Это будет правильноутилизируйте все созданные вами потоки независимо от того, пусты они или нет.

0 голосов
/ 14 июля 2015

Вот простая оболочка, которая позволяет вам распоряжаться любым IEnumerable с помощью using (чтобы сохранить тип коллекции вместо преобразования в IEnumerable, нам потребуются вложенные универсальные типы параметров , которые C # не выглядит для поддержки ):

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);
    }
}
0 голосов
/ 11 апреля 2014

Описание

Я придумал общее решение проблемы:)
Для меня было важно то, что все правильно расположено, даже если я не повторяюсьполное перечисление, которое имеет место, когда я использую такие методы, как 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);
        }
    }
    
...