2010-09-11 2 views
3

Что лучше всего подходит для вызова Dispose() на элементах последовательности?Как вы относитесь к последовательностям IDisposable с помощью LINQ?

Предположит, что есть что-то вроде:

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>, не пытаясь обобщать? Это так, потому что, если это нетипичная ситуация? Вы проектируете вокруг его в первую очередь? Если да, то как?

+1

Что мне не нравится в этом вопросе, так это то, что без конкретного сценария, который вы пытаетесь достичь, все усилия в ответах могут быть полностью отключены. Это делает интересную дискуссию, но это может быть далеко не решение реального решения. – eglasius

+0

Специализированное решение для конкретного сценария, скорее всего, было бы тривиальным, как показывают до сих пор ответы. Мне интересно, есть ли более обобщенный подход к передаче IEnumerable . Думаю, создание монады вокруг IDisposable - шаг в этом направлении. – chase

+0

Я не думаю, что ваш пример метода - хорошая идея. Методы LINQ не должны иметь побочных эффектов для перечисляемых значений, а у вас довольно серьезный. – zneak

ответ

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, как вы можете прочитать эту статью, Марк Gravell:

+0

Это предполагает, что в коде есть определенное место, где вы можете это добавить. Также он не уничтожает пустые потоки, как только будет определено, что они пусты и больше не нужны. – chase

+0

Я также хотел бы сказать, что если исключение выбрано из "stream.Dispose()" (например, NullRefException), это также предотвратит закрытие потоков, нет? – Alxandr

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

EDIT Для этих ограничений, может быть, 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; 
+0

+1: Я думаю, что вы правы в отношении временной копии без ToArray: я этого не заметил. –

+0

Хороший вопрос об повторении во второй раз. Именно поэтому я ищу шаблон, в котором экземпляры будут удалены, как только они будут использованы, поэтому во время второй итерации «старые» экземпляры больше не будут существовать (или, по крайней мере, не удерживаться на ресурсах). – chase

+0

@chase Если это вас беспокоит, как я уже сказал, превращение последовательности в массив предотвратит дальнейшие оценки селектора, поэтому вы не получите более одной копии каждого потока. – zneak

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

Я об этом думал. Но предположим, что метод возвращает DisposableCollection <>. Это означает, что вы либо отказываетесь от возможности фильтровать результат (или теряете «одноразовость»), либо теряете беглость LINQ, либо должны реализовать свои собственные методы Where() и т. Д., Заставляя их возвращать DisposableCollection <> as Что ж. – chase

0

Описание

Я придумать общее решение проблемы :)
Одна из вещей, которые были важны для меня было что все правильно настроено, даже если я не перебираю все перечисление, что имеет место, когда я использую такие методы, как FirstOrDefault (что я часто делаю).

Итак, я придумал пользовательский перечислитель, который обрабатывает все распоряжения. Все, что вам нужно сделать, это позвонить 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); 
        } 
    } 
    
0

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

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

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

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