2012-04-23 22 views
9

我有一些代碼有很多重複。問題來自我正在處理嵌套IDisposable類型的事實。今天,我有東西,看起來像:一個重構代碼如何能夠嵌套使用?

public void UpdateFromXml(Guid innerId, XDocument someXml) 
{ 
    using (var a = SomeFactory.GetA(_uri)) 
    using (var b = a.GetB(_id)) 
    using (var c = b.GetC(innerId)) 
    { 
     var cWrapper = new SomeWrapper(c); 
     cWrapper.Update(someXml); 
    } 
} 

public bool GetSomeValueById(Guid innerId) 
{ 
    using (var a = SomeFactory.GetA(_uri)) 
    using (var b = a.GetB(_id)) 
    using (var c = b.GetC(innerId)) 
    { 
     return c.GetSomeValue(); 
    } 
} 

整個嵌套using塊是每種方法相同(顯示了兩個,但也有他們的約10次)。唯一不同的是當你進入using塊的內層時會發生什麼。我在想

一種方法是將線沿線的做一些事情:

public void UpdateFromXml(Guid innerId, XDocument someXml) 
{ 
    ActOnC(innerId, c => 
    { 
     var cWrapper = new SomeWrapper(c); 
     cWrapper.Update(someXml); 
    }); 
} 

public bool GetSomeValueById(Guid innerId) 
{ 
    var result = null; 

    ActOnC(innerId, c => { result = c.GetSomeValue(); }); 

    return result; 
} 

private void ActOnC(Guid innerId, Action<TheCType> action) 
{ 
    using (var a = SomeFactory.GetA(_uri)) 
    using (var b = a.GetB(_id)) 
    using (var c = b.GetC(innerId)) 
    { 
     action(c); 
    }   
} 

這個工作,它只是一種笨重解析(人類)。 有沒有人有任何其他的建議,可以減少嵌套的using塊這樣的代碼重複?如果他們不是IDisposable那麼人們可能會創建一個方法來返回b.GetC(innerId)的結果......但這不是這裏的情況。

+3

+1我沒有看到任何解決方案中的笨重。這是一種非正統的比程序更具功能性,但我會把它算作專業人士,而不是一個騙子 – mfeingold 2012-04-23 17:49:39

+1

我認爲你的實現看起來不錯,但你可能會更喜歡下面提供的一些替代方案。如果你發現自己必須鏈接很多一次性用品,你可能需要重新設計一些東西,以免在這種情況下出現。 – Thomas 2012-04-23 17:58:45

回答

1

我喜歡BFree提供的答案作爲開始,但我會做一些修改。

//Give it a better name; this isn't designed to be a general purpose class 
public class MyCompositeDisposable : IDisposable 
{ 
    public MyCompositeDisposable (string uri, int id, int innerid) 
    { 
     A = SomeFactory.GetA(uri); 
     B = A.GetB(id); 
     C = B.GetC(innerId); 
    } 

    //You can make A & B private if appropriate; 
    //not sure if all three or just C should be exposed publicly. 
    //Class names are made up; you'll need to fix. 
    //They should also probably be given more meaningful names. 
    public ClassA A{get;private set;} 
    public ClassB B{get;private set;} 
    public ClassC C{get;private set;} 

    public void Dispose() 
    { 
     A.Dispose(); 
     B.Dispose(); 
     C.Dispose(); 
    } 
} 

這樣做,你可以這樣做後:

public bool GetSomeValueById(Guid innerId) 
{ 
    using(MyCompositeDisposable d = new MyCompositeDisposable(_uri, _id, innerId)) 
    { 
     return d.C.GetSomeValue(); 
    } 
} 

注意,MyCompositeDisposable將可能需要在構造函數中的try/finally塊和Dispose方法,以便在創建錯誤/破壞正常確保沒有任何結果不被處置。

+0

將這一切全部包裝到這樣的一個類中的想法非常適合我的需求,併爲我的所有案例提供了代碼重複數據刪除和靈活性的恰當平衡,此外它甚至有助於分離關注點。這幾乎是所有答案中最好的。謝謝。 – ckittel 2012-04-25 11:34:59

+0

這與BFree的答案具有相同的缺陷 - 在施工C期間的例外將使A和B不處理。 – 2012-04-25 12:41:36

+1

@DavidB在答案結尾處我已經有了一個註釋,那就是需要這樣的錯誤檢查,但它並未包含在答案中。如果在OP的情況下需要,他知道他需要添加它。 – Servy 2012-04-25 13:43:39

0

如果您的Dispoable類型正確處置所有一次性成員,則只需要一條使用語句。

例如,這樣的:

public bool GetSomeValueById(Guid innerId) 
{ 
    using (var a = SomeFactory.GetA(_uri)) 
    { 
     return a.GetSomeValue(); 
    } 
} 

class A : IDisposable 
{ 
    private a; 
    private b; 

    public A (B b, C c) 
    { 
    this.b = b; this.c = c; 
    } 

    public void Dispose() 
    { 
    Dispose(true); 
    } 

    protected void Dispose(bool disposing) 
    { 
    if (disposing) 
    { 
     b.Dispose(); 
     c.Dispose(); 
    } 
    } 
} 

您可以:

public bool GetSomeValueById(Guid innerId) 
{ 
    using (var a = SomeFactory.GetA(_uri)) 
    using (var b = a.GetB(_id)) 
    using (var c = b.GetC(innerId)) 
    { 
     return c.GetSomeValue(); 
    } 
} 

可能,如果類型的B和C,並在其處置方法處置的B和C的有成員成爲本但是,必須修改工廠以將b和c注入到a中。

+2

當對象處理由另一個類給予的對象時,應該小心。如果多個實例依賴於該對象會怎樣?處置通常應該是擁有類的責任,在這種情況下'A'不擁有'b'和'c'。 – Thomas 2012-04-23 17:53:29

+0

@Thomas優點。通常情況下,您還可以使用布爾ctor參數來指示A是否擁有b和c。 – jrummell 2012-04-23 17:56:20

1

在RX框架,有一個名爲類CompositeDisposablehttp://msdn.microsoft.com/en-us/library/system.reactive.disposables.compositedisposable%28v=vs.103%29.aspx

應該不會太難推出自己的(儘管非常精簡版本):

public class CompositeDisposable : IDisposable 
{ 
    private IDisposable[] _disposables; 

    public CompositeDisposable(params IDisposable[] disposables) 
    { 
     _disposables = disposables; 
    } 

    public void Dispose() 
    { 
     if(_disposables == null) 
     { 
      return; 
     } 

     foreach(var disposable in _disposables) 
     { 
      disposable.Dispose(); 
     } 
    } 
} 

那麼這看起來乾淨了一點:

public void UpdateFromXml(Guid innerId, XDocument someXml) 
{ 
    var a = SomeFactory.GetA(_uri); 
    var b = a.GetB(_id); 
    var c = b.GetC(innerId); 
    using(new CompositeDisposable(a,b,c)) 
    { 
     var cWrapper = new SomeWrapper(c); 
     cWrapper.Update(someXml); 
    } 
} 
+2

如果在b.GetC期間發生異常,我該怎麼辦? - 當發生這種情況時,我不認爲a和b正確處置。 – 2012-04-23 18:16:43

1

您總是可以創建一個更大的上下文來管理應該創建/處理哪些對象。然後編寫一個方法來創建更大的上下文...

public class DisposeChain<T> : IDisposable where T : IDisposable 
{ 
    public T Item { get; private set; } 
    private IDisposable _innerChain; 

    public DisposeChain(T theItem) 
    { 
     this.Item = theItem; 
     _innerChain = null; 
    } 

    public DisposeChain(T theItem, IDisposable inner) 
    { 
     this.Item = theItem; 
     _innerChain = inner; 
    } 

    public DisposeChain<U> Next<U>(Func<T, U> getNext) where U : IDisposable 
    { 
     try 
     { 
      U nextItem = getNext(this.Item); 
      DisposeChain<U> result = new DisposeChain<U>(nextItem, this); 
      return result; 
     } 
     catch //an exception occurred - abort construction and dispose everything! 
     { 
      this.Dispose() 
      throw; 
     } 
    } 

    public void Dispose() 
    { 
     Item.Dispose(); 
     if (_innerChain != null) 
     { 
      _innerChain.Dispose(); 
     } 
    } 
} 

然後使用它:

public DisposeChain<DataContext> GetCDisposeChain() 
    { 
     var a = new DisposeChain<XmlWriter>(XmlWriter.Create((Stream)null)); 
     var b = a.Next(aItem => new SqlConnection()); 
     var c = b.Next(bItem => new DataContext("")); 

     return c; 
    } 

    public void Test() 
    { 
     using (var cDisposer = GetCDisposeChain()) 
     { 
      var c = cDisposer.Item; 
      //do stuff with c; 
     } 
    }