2012-04-23 27 views
9

Mam kod, który ma strasznie dużo duplikacji. Problem wynika z faktu, że mam do czynienia z typami zagnieżdżonymi IDisposable. Dzisiaj mam coś, co wygląda następująco:W jaki sposób jeden kod refaktora może być użyty w zagnieżdżonych operacjach?

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

Cały zagnieżdżona using blok jest taki sam dla każdej z tych metod (dwa są pokazane, ale istnieje około dziesięciu z nich). Jedyną rzeczą, która jest inna, jest to, co dzieje się, gdy dojdziesz do wewnętrznego poziomu bloków using.

Jednym ze sposobów myślałem byłoby zrobić coś wzdłuż linii:

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

To działa, to po prostu rodzaj przylegający do analizowania (jako człowiek). Czy ktoś ma jakieś inne sugestie, w jaki sposób można zmniejszyć powielanie kodu wokół tak zagnieżdżonych bloków using? Jeśli nie byłyby to IDisposable, najprawdopodobniej wystarczyłaby metoda zwracania wyników b.GetC(innerId) ... ale tak nie jest w tym przypadku.

+3

+1 Nie widzę niczego przylegający w roztworze. Jest to trochę niekonwencjonalne, bardziej funkcjonalne niż proceduralne, ale uważam to za pro, a nie con – mfeingold

+1

Myślę, że twoja implementacja wygląda dobrze, ale może wolisz niektóre z alternatyw podanych poniżej. Jeśli okaże się, że musisz łączyć wiele artykułów jednorazowego użytku, możesz popatrzeć na przeprojektowanie rzeczy, aby nie znaleźć się w takiej sytuacji. – Thomas

Odpowiedz

1

Lubię odpowiedź dostarczoną przez BFree jako początek, ale chciałbym wprowadzić kilka modyfikacji.

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

Po wykonaniu, że można zrobić coś takiego:

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

nocie że MyCompositeDisposable prawdopodobnie będzie trzeba mieć try/finally bloków w konstruktorze i utylizować metod tak, że błędy w tworzeniu/niszczenie prawidłowo upewnić się, że nic nie zostanie unieważnione.

+0

Pomysł zapakowania go w taką klasę idealnie pasuje do moich potrzeb i oferuje właściwą równowagę między dedukcją kodu i elastycznością we wszystkich moich przypadkach, a nawet pomaga w oddzieleniu obaw. To było najlepsze ze wszystkich odpowiedzi. Dzięki. – ckittel

+0

Ma to tę samą wadę, co odpowiedź BFree - wyjątek podczas budowy C pozostawia A i B nieusunięte. –

+1

@DavidB Już miałem notatkę na końcu odpowiedzi, że takie sprawdzanie błędów jest potrzebne, ale nie zostało to uwzględnione w odpowiedzi tutaj. Jeśli jest to potrzebne w przypadku PO, to wie, że musi go dodać. – Servy

0

Jeśli twoje typy Dispoable poprawnie pozbywają się wszystkich elementów jednorazowych, potrzebujesz tylko jednej instrukcji użycia.

Na przykład ten:

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

może stać się to, jeśli A miał członkowie typu B i C, a zbyte b i c w sposobie rozporządzania:

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

byś musieli zmodyfikować fabrykę, aby wstrzyknąć b i c do a.

+2

Powinieneś być ostrożny, gdy przedmioty mają do dyspozycji przedmioty przekazane im przez inną klasę. Co jeśli więcej niż jedna instancja polega na tym obiekcie? Usuwanie powinno być na ogół obowiązkiem klasy posiadającej, w tym przypadku "A" nie jest właścicielem "b" i "c". – Thomas

+0

@Thomas doskonały punkt. Zwykle miałbyś również boolowskie parametry ctor wskazujące, czy A ma b i c czy nie. – jrummell

1

W ramach Rx istnieje klasa o nazwie CompositeDisposablehttp://msdn.microsoft.com/en-us/library/system.reactive.disposables.compositedisposable%28v=vs.103%29.aspx

nie powinno być zbyt trudne, aby rzucić swój własny (aczkolwiek bardzo uproszczoną wersję):

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

Wtedy to wygląda trochę czystsze:

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

co zrobić, jeśli wystąpi wyjątek podczas b.GetC - Nie sądzę, że a i b są prawidłowo usuwane, kiedy to nastąpi. –

1

Zawsze można utworzyć większy kontekst, w którym można zarządzać, które obiekty powinny zostać utworzone/usunięte. Następnie napisz metodę, aby utworzyć większy kontekst ...

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

Następnie użyj go:

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; 
     } 
    }