volviendo en medio de un bloque de uso

196

Algo como:

using (IDisposable disposable = GetSomeDisposable())
{
    //.....
    //......
    return Stg();
}

Creo que no es un lugar adecuado para una declaración de devolución, ¿verdad?

tafa
fuente

Respuestas:

194

Como varios otros han señalado en general, esto no es un problema.

El único caso que le causará problemas es si regresa en medio de una declaración de uso y, adicionalmente, devuelve la variable de uso de. Pero, de nuevo, esto también le causaría problemas, incluso si no regresó y simplemente mantuvo una referencia a una variable.

using ( var x = new Something() ) { 
  // not a good idea
  return x;
}

Igual de malo

Something y;
using ( var x = new Something() ) {
  y = x;
}
JaredPar
fuente
1
Estaba a punto de editar mi pregunta sobre el punto que mencionaste. Gracias.
tafa
Por favor, ayúdame a entender por qué esto es malo. Me gustaría devolver un flujo que estoy usando en una función auxiliar a otra función para el procesamiento de imágenes. Parece que la transmisión se eliminará si hago esto?
John Shedletsky
3
@JohnShedletsky En este caso, su llamada a la función debe envolverse con el uso. Como usar (Stream x = FuncToReturnStream ()) {...} y no usar dentro de FuncToReturnStream.
Felix Keil
@JohnShedletsky Estoy seguro de que es porque la returndeclaración hace que el final del usingbloque sea inaccesible para cualquier ruta de código. El final del usingbloque debe ejecutarse para que el objeto pueda eliminarse si es necesario.
facepalm42
147

Está perfectamente bien

Aparentemente estás pensando que

using (IDisposable disposable = GetSomeDisposable())
{
    //.....
    //......
    return Stg();
}

se traduce ciegamente a:

IDisposable disposable = GetSomeDisposable()
//.....
//......
return Stg();
disposable.Dispose();

Lo cual, sin duda, sería un problema y haría que la usingafirmación sea bastante inútil, razón por la cual eso no es lo que hace.

El compilador se asegura de que el objeto esté dispuesto antes de que el control abandone el bloque, independientemente de cómo lo abandone.

James Curran
fuente
77
Yo estaba, aparentemente.
tafa
Gran respuesta @ James Curran! Pero me da bastante curiosidad en qué se traduce. ¿O es eso solo expresable en IL? (que nunca he intentado leer antes).
Bart
1
@Bart: creo que evalúa la expresión de retorno en una variable temporal, luego hace la disposición y luego devuelve la variable temporal.
ToolmakerSteve
@James Curran. De arriba a aquí, solo tú explicaste lo que sucedió en el fondo. Muchas gracias.
Sercan Timoçin
@Bart probablemente esté traducido a: intente {... su código ...} finalmente {x.Dispose (); }
Bip901
94

Está absolutamente bien, no hay problema en absoluto. ¿Por qué crees que está mal?

Una declaración de uso es solo azúcar sintáctico para un bloque try / finalmente, y como Grzenio dice que está bien regresar también de un bloque try.

Se evaluará la expresión de retorno, luego se ejecutará el bloque finalmente y luego se devolverá el método.

Jon Skeet
fuente
55
La respuesta de James Curran explica lo que estaba pensando.
tafa
27

Esto funcionará perfectamente bien, al igual que volver en medio de try{}finally{}

Grzenio
fuente
18

Eso es totalmente aceptable. Una instrucción de uso garantiza que el objeto IDisposable se eliminará sin importar qué.

De MSDN :

La instrucción de uso garantiza que se invoque Dispose incluso si se produce una excepción mientras está llamando a métodos en el objeto. Puede lograr el mismo resultado colocando el objeto dentro de un bloque try y luego llamando a Dispose en un bloque finalmente; de hecho, así es como el compilador traduce la declaración de uso.

mbillard
fuente
14

El siguiente código muestra cómo usingfunciona:

private class TestClass : IDisposable
{
   private readonly string id;

   public TestClass(string id)
   {
      Console.WriteLine("'{0}' is created.", id);
      this.id = id;
   }

   public void Dispose()
   {
      Console.WriteLine("'{0}' is disposed.", id);
   }

   public override string ToString()
   {
      return id;
   }
}

private static TestClass TestUsingClose()
{
   using (var t1 = new TestClass("t1"))
   {
      using (var t2 = new TestClass("t2"))
      {
         using (var t3 = new TestClass("t3"))
         {
            return new TestClass(String.Format("Created from {0}, {1}, {2}", t1, t2, t3));
         }
      }
   }
}

[TestMethod]
public void Test()
{
   Assert.AreEqual("Created from t1, t2, t3", TestUsingClose().ToString());
}

Salida:

Se crea 't1'.
Se crea 't2'.
Se crea 't3'.
Se crea 'Creado a partir de t1, t2, t3'.
't3' está dispuesto.
't2' está dispuesto.
't1' está dispuesto.

Los eliminados se invocan después de la declaración de devolución pero antes de la salida de la función.

Bertrand
fuente
1
Tenga en cuenta que algunos objetos de C # se eliminan de manera personalizada, por ejemplo, los clientes WCF son una declaración de uso como la anterior, devuelve "no se puede acceder al objeto dispuesto"
OzBob
-4

Quizás no sea 100% cierto que esto sea aceptable ...

Si sucede que está anidando usos y regresando de uno anidado, puede que no sea seguro.

Toma esto como un ejemplo:

using (var memoryStream = new MemoryStream())
{
    using (var textwriter = new StreamWriter(memoryStream))
    {
        using (var csv = new CsvWriter(textwriter))
        {
            //..write some stuff to the stream using the CsvWriter
            return memoryStream.ToArray();
        }
    }
}

Estaba pasando una DataTable para que se muestre como csv. Con el retorno en el medio, estaba escribiendo todas las filas en la secuencia, pero al csv de salida siempre le faltaba una fila (o varias, dependiendo del tamaño del búfer). Esto me dijo que algo no se estaba cerrando correctamente.

La forma correcta es asegurarse de que todos los usos anteriores se eliminen correctamente:

using (var memoryStream = new MemoryStream())
{
    using (var textwriter = new StreamWriter(memoryStream))
    {
        using (var csv = new CsvWriter(textwriter))
        {
            //..write some stuff to the stream using the CsvWriter
        }
    }

    return memoryStream.ToArray();
}
Sí claro
fuente