2010-05-16 96 views
24

有時我需要在一個函數中使用幾個一次性對象。最常見的情況是有StreamReader和StreamWriter,但有時甚至比這更多。擺脫嵌套使用(...)語句

嵌套使用語句快速加起來,看起來很醜。 爲了彌補這一點,我創建了一個小類,收集IDisposable對象,並在處置它們時處置它們。

public class MultiDispose : HashSet<IDisposable>, IDisposable 
{ 
    public MultiDispose(params IDisposable[] objectsToDispose) 
    { 
     foreach (IDisposable d in objectsToDispose) 
     { 
      this.Add(d); 
     } 
    } 

    public T Add<T>(T obj) where T : IDisposable 
    { 
     base.Add(obj); 
     return obj; 
    } 

    public void DisposeObject(IDisposable obj) 
    { 
     obj.Dispose(); 
     base.Remove(obj); 
    } 


    #region IDisposable Members 

    public void Dispose() 
    { 
     foreach (IDisposable d in this) 
     { 
      d.Dispose(); 
     } 

    } 

    #endregion 
} 

所以我的代碼現在看起來像這樣:

 using (MultiDispose md = new MultiDispose()) 
     { 
      StreamReader rdr = md.Add(new StreamReader(args[0])); 
      StreamWriter wrt = md.Add(new StreamWriter(args[1])); 
      WhateverElseNeedsDisposing w = md.Add(new WhateverElseNeedsDisposing()); 

      // code 
     } 

有什麼不對這種方法可能會導致在路上的問題? 我離開了繼承自HashSet的Remove函數,以便該類更加靈活。 當然,濫用這個功能可能會導致物體沒有被正確處理,但是還有很多其他的方法可以在沒有這個課程的情況下在腳下拍攝自己。

+0

-1:問題太主觀 – Simon 2010-05-16 19:08:54

+1

海事組織 - 在許多層面上只是一個壞主意。我希望你在這裏找到指導,爲了你自己和你可能工作的任何團隊成員。請參閱Skeet的回答。 – 2010-05-16 19:26:13

+9

@Simon:別傻了。我們在這裏一直都有關於命名,資本化和縮進的問題。相比之下,這是相當可觀的。 – 2010-05-16 19:42:00

回答

44

你可能只是這樣做:

using (var a = new A()) 
using (var b = new B()) 
{ 
    /// ... 
} 
+1

雖然三個人說了同樣的話,但我只會添加此評論一次。如果你將stylecop作爲你的編碼標準的一部分,我不認爲它會這樣,因爲它違反了禁止使用大括號的規則 – Stewart 2010-05-16 19:07:44

+2

Dang!這就是IntelliSense自學語言對你的幫助!我已經使用C#多年了,並沒有想過用這種方式使用陳述鏈接:( – Ghostrider 2010-05-16 19:09:31

+9

@Stewart如果StyleCop不喜歡這樣StyleCop是錯誤的。 – Will 2010-05-16 19:10:55

12

也許這是你表現出一個簡單的例子,但我認爲以下是更具可讀性。

using (StreamReader rdr = new StreamReader(args[0])) 
using (StreamWriter wrt = new StreamWriter(args[1])) 
{  
    // code 
} 
6

您可以嵌套using報表更漂亮的只用一對大括號,就像這樣:

using (StreamReader rdr = new StreamReader(args[0])) 
using (StreamWriter wrt = new StreamWriter(args[1])) 
{ 
    ///... 
} 

要回答你的問題,你需要在另外的相反順序處置。
因此,您不能使用HashSet

另外,沒有理由向外界公開IDisposable的列表。
因此,您不應該繼承任何集合類,而應該維護私有的List<IDisposable>

然後,您應該有公開的Add<T>Dispose方法(和其他方法),並在Dispose中向後循環列表。

+3

+1對Hashset有很大的影響... – Josh 2010-05-16 19:07:56

4

個人而言,這會驅使我堅果。如果你發現使用嵌套語句會很煩人,你可以回到try/finally語法。 Dispose方法不應該拋出異常,因此您可以假設多個一次性函數不需要單獨封裝在try/finally塊中。

另外值得一提的是,你只需要一組括號內的相鄰使用塊,如:

using (var stream = File.Open(...)) 
using (var reader = new StreamReader(stream)) { 

    // do stuff 

} 
20

有關的一般原則的幾點:

  • 你的代碼是明顯不地道C#。基本上,你要求任何與你的代碼一起工作的人都能以不尋常的風格獲得很少的好處。
  • 正如其他人所指出的那樣,你可以窩using報表而無需額外的括號
  • 如果您有很多在一個單一的方法使用的語句發現自己,你可能要考慮分解成更小的方法
  • 如果你有兩個相同類型的變量,你可以使用一個using語句:

    using (Stream input = File.OpenRead("input.dat"), 
         output = File.OpenWrite("output.dat")) 
    { 
    } 
    

現在假設你真的想與THI先走s:

  • 您的代碼將以難以預測的順序處置其包含的資源。它應該嵌入一個列表,然後以與Add的調用相反的順序處理事件。
  • 沒有理由HashSet<T>派生或實際上任何集合。您應該在的列表中將該列表作爲私有成員變量。
  • 如果其中一個Dispose呼叫失敗,則其他Dispose呼叫都不會進行;與傳統的using聲明一樣,每個呼叫Dispose都在其自己的finally區塊內進行。

基本上,我認爲這是一個壞主意。嵌套兩層深度遠非痛苦;築巢三應該是罕見的;嵌套四個或更多強烈暗示重構。不要試圖應付深層嵌套的痛苦,你應該設計遠離它。

+1

對於'壞主意'+1。我的感覺在那些隱含的塊中會變得更遠,除非代碼再也不會被觸及,這些bug是否等待發生。我總是毫無例外地使用明確的塊。正如你所說,如果我被嵌套困擾,我會遠離它。 – 2010-05-16 19:24:39

+0

我不完全確定,但是我對使用語句的理解意味着您的兩個相同類型變量的示例是不安全的。我非常肯定,這兩個對象不能相互依靠開始,因爲構造順序可能是非確定性的,但是如果第二個構造函數拋出,我很確定第一個對象不會被拋棄。 – Stewart 2010-05-16 19:29:31

+0

+1建議分解成更小的方法。另外,+1 @Sky建議總是有塊,沒有例外。 – Travis 2011-05-06 13:59:49

3

我不得不說我不同意誰想要使用報表一前一後喜歡做的人:

using (var a = new StreamReader()) 
using (var b = new StreamWriter()) 
{ 
// Implementation 
} 

在我看來,這是非常不可讀 - 具有不是包裹的任何代碼塊只是不好的風格,除非所有開發人員都非常小​​心,否則可能會導致問題。

我把那看齊類似:

if (someBoolean) DoSomething(); 
{ 
    // Some code block unrelated to the if statement 
} 

從技術上講這不是無效的,但是這太可怕了一下。

我同意MultiDispose概念可能不是最好的主意,由於它不是一個可接受的模式,但我絕對不會去這條路線。如果你不能把東西分解成更小的塊,那麼我建議只使用嵌套使用。