¿Es un buen enfoque llamar a return inside usando la instrucción {}?

93

Solo quiero saber si es seguro / buen enfoque llamar returndentro de un usingbloque.

Por ej.

using(var scope = new TransactionScope())
{
  // my core logic
  return true; // if condition met else
  return false;
  scope.Complete();
}

Sabemos dispose()que se cancelará la última llave más rizada . Pero lo que será en el caso anterior, ya que returnsalta el control fuera del alcance dado (AFAIK) ...

  1. ¿Me scope.Complete()llaman?
  2. Y así para el dispose()método del osciloscopio .
Paolo Moretti
fuente
1
Una vez que using{}se termina el alcance, los objetos relevantes se eliminan, return"romperán" el alcance, por lo que los objetos se eliminarán como se esperaba
Shai
4
Tenga en cuenta que su scope.Complete()llamada nunca se verá afectada con la muestra que proporcionó, por lo que su transacción siempre se revertirá.
Andy
Independientemente de si se llama a using's dispose(), cuando regrese, la función que contiene este usingbloque habrá regresado y todo lo que le pertenezca quedará huérfano. Entonces, incluso si scopeno se hubiera eliminado "por using" (lo será, como otros explicaron), se eliminará de todos modos porque la función terminó. Si C # tuviera una gotodeclaración, ¿ya terminaste de reír? bueno, entonces en lugar de regresar, podría gotohacerlo después de la llave de cierre, sin regresar. Lógicamente, scopetodavía se eliminaría, pero acaba de poner gotoC #, así que a quién le importa la lógica en esa etapa.
Superbest
C # tiene goto .
Jake T.

Respuestas:

146

Es perfectamente seguro llamar returndentro de su usingbloque, ya que un bloque de uso es solo un try/finallybloque.

En su ejemplo anterior, después de la devolución true, el alcance se eliminará y se devolverá el valor. return falseY scope.Complete()se no ser llamado. Disposesin embargo, se llamará independientemente, ya que reside dentro del bloque finalmente.

Su código es esencialmente el mismo que este (si eso lo hace más fácil de entender):

var scope = new TransactionScope())
try
{
  // my core logic
  return true; // if condition met else
  return false;
  scope.Complete();
}
finally
{
  if( scope != null) 
    ((IDisposable)scope).Dispose();
}

Tenga en cuenta que su transacción nunca se confirmará, ya que no hay forma de llegar scope.Complete()a confirmar la transacción.

Øyvind Bråthen
fuente
13
Debe dejar en claro que Dispose se llamará. Si el OP no sabe qué sucede en using, es probable que no sepa qué sucede finally.
Konrad Rudolph
Está bien dejar un bloque de uso con devolución, pero en el caso de TransactionScope, es posible que tenga problemas con la declaración de uso en sí: blogs.msdn.com/b/florinlazar/archive/2008/05/05/8459994.aspx
thewhiteambit
En mi experiencia, esto no funciona con los ensamblados CLR de SQL Server. Cuando necesito devolver resultados para una UDF que contiene un campo SqlXml que hace referencia a un objeto MemoryStream. Recibo " No se puede acceder a un objeto eliminado " e " Intento no válido de llamar a Read cuando la secuencia está cerrada ", por lo que me veo FORZADO a escribir código con fugas y renunciar a la instrucción using en este escenario. :( Mi única esperanza es que SQL CLR maneje la eliminación de estos objetos. Es un escenario único, pero pensé en compartirlo.
MikeTeeVee
1
@MikeTeeVee - Las soluciones más limpias son (a) hacer que la persona que llama haga using, por ejemplo using (var callersVar = MyFunc(..)) .., en lugar de usar dentro de "MyFunc" - me refiero a que la persona que llama recibe la transmisión y es responsable de cerrarla vía usingo explícitamente, o (b) Haga que MyFunc extraiga toda la información necesaria en otros objetos, que se pueden transferir de forma segura; luego, su using. No debería tener que escribir código con fugas.
ToolmakerSteve
7

Eso está bien: las finallycláusulas (que es lo que hace la llave de cierre de la usingcláusula debajo del capó) siempre se ejecutan cuando se deja el alcance, sin importar cómo.

Sin embargo, esto solo es cierto para las declaraciones que están en el bloque finalmente (que no se puede establecer explícitamente cuando se usa using). Por lo tanto, en su ejemplo, scope.Complete()nunca se llamaría (aunque espero que el compilador le advierta sobre el código inalcanzable).

Lucero
fuente
2

En general, es un buen enfoque. Pero en su caso, si regresa antes de llamar al scope.Complete(), simplemente eliminará el TransactionScope. Depende de tu diseño.

Entonces, en este ejemplo, no se llama a Complete () y se elimina el alcance, asumiendo que hereda la interfaz IDisposable.

M. Mennan Kara
fuente
Debe implementar IDisposable o usar wont compile.
Chriseyre2000
2

Alcance Definitivamente se debe llamar a Complete antes return. El compilador mostrará una advertencia y este código nunca se llamará.

Con respecto a returnsí mismo, sí, es seguro llamarlo usingdeclaración interna . El uso se traduce en intentar, finalmente bloquear detrás de la escena y finalmente bloquear debe ejecutarse.

Tony Kh
fuente
1

En el ejemplo que ha proporcionado, hay un problema; scope.Complete()nunca se llama. En segundo lugar, no es una buena práctica utilizar returndeclaraciones dentro de usingdeclaraciones. Consulte lo siguiente:

using(var scope = new TransactionScope())
{
    //have some logic here
    return scope;      
}

En este simple ejemplo, el punto es que; el valor de scopeserá nulo cuando finalice el uso de la instrucción.

Así que es mejor no volver al interior usando declaraciones.

daryal
fuente
1
El hecho de que 'alcance de devolución' no tenga sentido, no implica que la declaración de devolución sea incorrecta.
Preet Sangha
el hecho de que no utilice las mejores prácticas no implica que haya hecho algo mal. Implica que es mejor evitarlo, ya que puede tener consecuencias imprevistas.
daryal
1
El valor de scopeno será nula - la única cosa que va a haber sucedido es que Dispose()se han invocado en esa instancia, y por lo tanto la instancia debe no utilizar más (pero no es nula y no hay nada que impida que pruebe y el uso el objeto desechado, aunque se trate de un uso inadecuado de un objeto desechable).
Lucero
Lucero tiene toda la razón. Los objetos desechables no son nulos después de ser eliminados. Su propiedad IsDisposed es verdadera, pero si contrasta null, obtiene falso y return scopedevuelve una referencia a ese objeto. De esta forma, si asigna esa referencia a la devolución, evita que el GC limpie el objeto desechado.
ThunderGr
1

Para asegurarse de que scope.Complete()se llamará, envuélvalo con try/finally. Se disposellama porque lo ha envuelto con el usingque es un try/finallybloque alternativo .

using(var scope = new TransactionScope())
{
  try
  {
  // my core logic
  return true; // if condition met else
  return false;
  }
  finally
  {
   scope.Complete();
  }
}
Aristos
fuente
Creo que quieres decir Si ganaste - Si quieres, no no lo harás ... según tu código. :)
0

En este ejemplo, scope.Complete () nunca se ejecutará. Sin embargo, el comando return limpiará todo lo que esté asignado en la pila. El GC se encargará de todo lo que no esté referenciado. Por lo tanto, a menos que haya un objeto que el GC no pueda recoger, no hay problema.

ThunderGr
fuente