2010-05-16 33 views
24

A veces necesito usar varios objetos desechables dentro de una función. El caso más común es tener StreamReader y StreamWriter, pero a veces es incluso más que esto.Deshacerse de anidado utilizando sentencias (...)

Declaraciones de uso anidado suman rápidamente y se ven feas. Para remediar esto, he creado una clase pequeña que recoge los objetos IDisposables y los descarta cuando se desecha.

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 
} 

Así que mi código ahora se ve así:

 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 
     } 

¿Hay algo malo con este enfoque que puede causar problemas en el futuro? Dejé la función Remove heredada del HashSet a propósito para que la clase fuera más flexible. Sin duda, el mal uso de esta función puede hacer que los objetos no se eliminen correctamente, pero hay muchas otras maneras de dispararse en el pie sin esta clase.

+0

-1: Pregunta demasiado subjetiva – Simon

+1

IMO - solo una mala idea en muchos niveles. Espero que encuentres una guía aquí, por tu propio bien y el de cualquier miembro del equipo con el que puedas trabajar. Vea la respuesta de Skeet para mayor claridad. –

+9

@Simon: no seas tonto. Aquí tenemos preguntas sobre nombres, uso de mayúsculas y sangría todo el tiempo. Este es bastante sustancial en comparación. –

Respuesta

44

Se podía hacer esto:

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

Solo añadiré este comentario una vez, aunque tres personas hayan dicho lo mismo. Si tiene stylecop como parte de su estándar de codificación, no creo que le guste esto porque no cumple con la regla que prohíbe si no tiene llaves – Stewart

+2

Dang! ¡Eso es lo que el autoaprendizaje de un lenguaje de IntelliSense te hace a ti!He usado C# durante años y no he pensado en encadenar usando declaraciones de esta manera :( – Ghostrider

+9

@Stewart si a StyleCop no le gusta este StyleCop está equivocado. – Will

12

tal vez es sólo que ha mostrado un ejemplo sencillo, pero creo que el siguiente es más fácil de leer.

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

Puede hacer using instrucciones anidadas más bonita utilizando sólo un par de llaves, así:

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

Para responder a su pregunta, es necesario disponer en el orden inverso de adición .
Por lo tanto, no puede usar un HashSet.

Además, no hay razón para exponer la lista de IDisposable s al mundo exterior.
Por lo tanto, no debe heredar ninguna clase de colección, y en su lugar mantener un List<IDisposable> privado.

Debería tener los métodos Add<T> y Dispose públicos (y ningún otro método), y recorrer la lista al revés en Dispose.

+3

+1 gran captura en el Hashset ... – Josh

4

Personalmente, esto me volvería loco. Si considera que anidar usando declaraciones es molesto, puede volver a la sintaxis try/finally. Los métodos de eliminación no deben arrojar excepciones, por lo que podría suponer que no sería necesario envolver varios elementos desechables individualmente en bloques try/finally.

También digno de mención es que sólo se necesita un conjunto de soportes para el uso de los bloques adyacentes como:

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

    // do stuff 

} 
20

algunos puntos sobre el principio general:

  • su código es claramente no-idiomática DO#. Básicamente le estás pidiendo a cualquier otra persona que trabaje con tu código que incorpore un estilo inusual con muy poco beneficio.
  • Como otros han señalado, puede anidar using declaraciones sin apoyos adicionales
  • Si usted se encuentra con mucha de la utilización de declaraciones en un solo método, es posible que desee considerar rompiéndolo en métodos más pequeños
  • Si tiene dos variables del mismo tipo, se puede usar una sola instrucción using:

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

Ahora suponiendo que realmente quiere seguir adelante con thi s:

  • Su código eliminará sus recursos contenidos en un orden difícil de predecir. En lugar de usar un conjunto, debe incluir una lista y luego deshacerse de las cosas en orden inverso a las llamadas al Add.
  • No hay ninguna razón para obtener de HashSet<T> o de hecho cualquier colección. Solo debe tener una lista dentro de la clase como una variable de miembro privada.
  • Si falla una de las llamadas Dispose, no se realizará ninguna de las otras llamadas Dispose; con una instrucción tradicional using, cada llamada a Dispose se realiza dentro de su propio bloque finally.

Básicamente, creo que es una mala idea. Anidar dos niveles de profundidad está lejos de ser doloroso; anidar tres debe ser raro; anidando cuatro o más fuertemente sugiere refactorización. En lugar de tratar de sobrellevar el dolor de la anidación profunda, debe diseñar lejos de ella.

+1

+1 por 'mala idea'. Mis sentimientos van un poco más allá en esos bloques implícitos, a menos que el código nunca vuelva a ser tocado, son errores que están por suceder. Yo siempre, sin excepción uso bloques explícitos. Y como dices, si me molesta la anidación, diseño lejos de ella. –

+0

No estoy del todo seguro, pero mi comprensión del uso de declaraciones significa que su ejemplo de dos variables del mismo tipo no es seguro. Estoy bastante seguro de que los dos objetos no podrán depender el uno del otro para empezar porque el orden de construcción probablemente no sea determinista, pero también si el segundo constructor lanza, estoy bastante seguro de que el primer objeto no será eliminado. – Stewart

+0

+1 por sugerir romper en métodos más pequeños. Además, +1 @Sky sugiere que siempre haya bloques sin excepción. – Travis

3

Tengo que decir que estoy de acuerdo con las personas que quieren hacer uso de declaraciones, uno tras otro, como:

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

En mi opinión, eso es muy ilegible - tener ningún bloque de código que no está envuelto es solo un mal estilo, y puede provocar problemas a menos que todos los desarrolladores que trabajan en él sean muy cuidadosos.

que me había puesto a la par con algo como:

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

Técnicamente no es válida, pero es horrible a la vista.

Estoy de acuerdo en que el concepto MultiDispose probablemente no es la mejor idea, debido a que no es un patrón aceptado, pero definitivamente tampoco seguiría esta ruta. Si no puedes dividir las cosas en pedazos más pequeños, entonces te sugiero que simplemente viva con los usos anidados.