2012-04-23 11 views
9

Tengo un código que tiene una gran cantidad de duplicaciones. El problema proviene del hecho de que estoy tratando con tipos anidados IDisposable. Hoy tengo algo que parece:¿Cómo podría un código de refactorización involucrarse en los usos anidados?

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

El conjunto anidado using bloque es el mismo para cada uno de estos métodos (se muestran dos, pero hay cerca de diez de ellos). Lo único que es diferente es lo que sucede cuando se llega al nivel interno de los bloques using.

Una forma Estaba pensando sería hacer algo en la línea de:

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

Esto funciona, es sólo un poco torpe para analizar (como un ser humano). ¿Alguien tiene alguna otra sugerencia sobre cómo se podría reducir la duplicación de código alrededor de bloques anidados using de esta manera? Si no fueran IDisposable, probablemente solo crearía un método para devolver los resultados de b.GetC(innerId) ... pero ese no es el caso aquí.

+3

+1 no veo nada torpe en su solución. Es algo poco ortodoxo siendo más funcional que de procedimiento, pero lo consideraría como un profesional, no como un – mfeingold

+1

Creo que su implementación se ve bien, pero tal vez prefiera algunas de las alternativas que se proporcionan a continuación. Si se ve obligado a encadenar muchos elementos desechables, puede considerar rediseñar las cosas para no terminar en esta situación. – Thomas

Respuesta

1

me gusta la respuesta proporcionada por BFree como punto de partida, pero me gustaría hacer algunas modificaciones.

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

Después de hacer eso se puede hacer algo como:

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

Tenga en cuenta que la MyCompositeDisposable probablemente necesita tener try/finally bloques en el constructor y Desechar métodos para que los errores en la creación/destrucción adecuadamente asegúrese de que nada termine sin deshacerse.

+0

La idea de envolver todo en una clase como esta es perfecta para mis necesidades y ofrece el equilibrio justo de de-duplicación de código y flexibilidad para todos mis casos, además de que incluso ayuda a separar un poco las preocupaciones. Esto tenía casi la mejor de todas las respuestas. Gracias. – ckittel

+0

Esto tiene el mismo defecto que la respuesta de BFree: una excepción durante la construcción de C dejará A y B no eliminados. –

+1

@DavidB Al final de la respuesta ya tenía una nota que indica que es necesaria dicha comprobación de errores, pero que no se incluyó en la respuesta aquí. Si es necesario en el caso del OP, sabe que necesita agregarlo. – Servy

0

Si sus tipos Dispoable eliminan correctamente todos los miembros desechables, solo necesitaría una instrucción using.

Por ejemplo, esto:

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

podría llegar a ser esto si A había miembros de b del tipo y C, y una dispuestos de byc en su método dispose:

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

Usted haría Sin embargo, tiene que modificar su fábrica para inyectar byc en a.

+2

Debe tener cuidado cuando los objetos se deshacen de los objetos que les da otra clase. ¿Qué sucede si más de una instancia se basa en ese objeto? La eliminación generalmente debe ser responsabilidad de la clase propietaria, y en este caso 'A' no posee' b' y 'c'. – Thomas

+0

@Thomas excelente punto. Normalmente, también tendría parámetros boolean ctor que indican si A posee o no b. – jrummell

1

En el marco Rx hay una clase llamada CompositeDisposablehttp://msdn.microsoft.com/en-us/library/system.reactive.disposables.compositedisposable%28v=vs.103%29.aspx

no debería ser demasiado difícil de rodar su propia (aunque muy simplificada versión):

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

Entonces esto parece un poco más limpio:

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

¿Qué sucede si ocurre una excepción durante b.GetC? No creo que a y b se eliminen correctamente cuando eso ocurra. –

1

Siempre puede crear un contexto más amplio para administrar qué objetos se deben crear/desechar. Luego, escribe un método para crear ese contexto más grande ...

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

luego usarlo:

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