2010-09-11 51 views
3

關於序列元素的最佳方法是什麼,請致電Dispose()如何處理使用LINQ的IDisposable序列?

假設有這樣的:

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 2010-09-12 01:44:39

+0

針對特定場景的專門解決方案很可能是微不足道的,至今爲止的答案都表明。如果有更通用的方法來處理IEnumerable ,我很感興趣。我想,在IDisposable周圍創建monad是朝着這個方向邁出的一步。 – chase 2010-09-12 05:37:25

+0

我不認爲你的方法例子是一個好主意。 LINQ方法不應該對枚舉值產生副作用,而且你的方法非常嚴重。 – zneak 2010-09-12 15:58:32

回答

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構造函數中的一個無法關閉的流。爲了解決這個問題,你需要更加小心構建inital列表:

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 2010-09-11 23:18:42

+0

我也想說,如果從「stream.Dispose()」(例如NullRefException)拋出異常,這也會阻止流被關閉,不是嗎? – Alxandr 2013-06-19 06:41:09

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; 
+0

+1:我認爲你對沒有ToArray的臨時副本是正確的:我沒有注意到這一點。 – 2010-09-11 23:19:30

+0

重複第二次的好處。這就是爲什麼我正在尋找一種模式,在這種模式下,實例一旦被使用就會被丟棄,因此在第二次迭代時,「舊」實例將不再存在(或者至少不能保留資源)。 – chase 2010-09-11 23:35:13

+0

@chase如果您擔心這一點,正如我所說的,將序列轉換爲數組將阻止對選擇器的進一步評估,因此您不會獲得每個流的多個副本。 – zneak 2010-09-11 23:38:07

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 <>作爲好。 – chase 2010-09-11 23:49:30

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

下面是一個簡單的包裝,讓您處理任何IEnumerableusing(保留集合類型,而不是鑄造爲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(); 
    } 
} 

這將妥善處置所有你創建,無論它們是否是空流。

相關問題