É uma boa abordagem chamar o retorno interno usando a instrução {}?

90

Só quero saber se é seguro / bom fazer chamadas returndentro de um usingbloco.

Por ex.

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

Sabemos que a última chave dispose()será cancelada. Mas o que será no caso acima, já que returnsalta o controle do escopo dado (AFAIK) ...

  1. É meu scope.Complete()chamado?
  2. E assim, para o dispose()método do osciloscópio .
Paolo Moretti
fonte
1
Assim que o using{}escopo terminar, os objetos relevantes returnserão descartados, irão "quebrar" o escopo - então os objetos serão descartados como esperado
Shai
2
Esteja ciente de que sua scope.Complete()chamada nunca será atingida com a amostra fornecida, então sua transação sempre será revertida.
Andy
Independentemente de se using's dispose()for chamado, quando você retornar, a função que contém este usingbloco terá retornado e tudo pertencente a ele ficará órfão. Portanto, mesmo se scopenão tivesse sido eliminado "pelo using" (será, como outros explicaram), será eliminado de qualquer maneira porque a função foi encerrada. Se C # tivesse a gotodeclaração - você já parou de rir? good- então em vez de retornar você poderia gotoapós a chave de fechamento, sem retornar. Logicamente, scopeainda estaria descartado, mas você acabou de inserir gotoC # para quem se importa com a lógica nesse estágio.
Excelente
C # tem goto .
Jake T.

Respostas:

142

É perfeitamente seguro chamar returndentro do seu usingbloco, uma vez que um bloco em uso é apenas umtry/finally bloco.

Em seu exemplo acima, após o retorno true, o escopo será descartado e o valor retornado. return false, e nãoscope.Complete() será chamado.Disposeno entanto, será chamado independentemente, pois reside dentro do bloco finally.

Seu código é essencialmente o mesmo (se isso o tornar mais 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();
}

Esteja ciente de que sua transação nunca será confirmada, pois não há como fazer scope.Complete()para confirmar a transação.

Øyvind Bråthen
fonte
13
Você deve deixar claro que Dispose será chamado. Se o OP não sabe o que acontece em using, é provável que ele não saiba o que acontece com finally.
Konrad Rudolph
Não há problema em deixar um bloco using com return, mas no caso do TransactionScope você pode ter problemas com a própria instrução using: blogs.msdn.com/b/florinlazar/archive/2008/05/05/8459994.aspx
thewhiteambit
Na minha experiência, isso não funciona com assemblies CLR do SQL Server. Quando preciso retornar resultados para um UDF contendo um campo SqlXml que faz referência a um objeto MemoryStream. Eu recebo " Não é possível acessar um objeto descartado " e " Tentativa inválida de chamar Read quando o fluxo é fechado. ", Portanto, sou FORÇADO a escrever código com vazamento e desistir da instrução de uso neste cenário. :( Minha única esperança é que o SQL CLR lide com o descarte desses objetos. É um cenário único, mas pensei em compartilhar.
MikeTeeVee
1
@MikeTeeVee - As soluções mais limpas podem (a) fazer com que o chamador faça using, por exemplo using (var callersVar = MyFunc(..)) .., em vez de usar dentro de "MyFunc" - quero dizer que o chamador recebe o fluxo e é responsável por fechá-lo via usingou explicitamente, ou (b) faça com que MyFunc extraia todas as informações necessárias para outros objetos, que podem ser passadas de volta com segurança - então os objetos de dados ou fluxo subjacentes podem ser descartados por seu using. Você não deve ter que escrever código que vaza.
Toolmaker Steve
6

Tudo bem - finallycláusulas (que é o que a chave de fechamento dousing cláusula faz sob o capô) sempre são executadas quando o escopo é deixado, não importa como.

No entanto, isso só é verdadeiro para as instruções que estão no bloco finally (que não podem ser explicitamente definidas ao usar using ). Portanto, no seu exemplo, scope.Complete()nunca seria chamado (espero que o compilador avise sobre código inacessível).

Lucero
fonte
2

Em geral, é uma boa abordagem. Mas, no seu caso, se você retornar antes de chamar o scope.Complete(), isso irá destruir o TransactionScope. Depende do seu design.

Portanto, neste exemplo, Complete () não é chamado e o escopo é descartado, supondo que ele herde a interface IDisposable.

M. Mennan Kara
fonte
Deve implementar IDisposable ou usando wont compile.
Chriseyre2000
2

scope.Complete definitivamente deve ser chamado antes return. O compilador exibirá um aviso e esse código nunca será chamado.

Quanto a returnsi mesmo - sim, é seguro chamá-lo de usingdeclaração interna . O uso é traduzido para tentar finalmente bloquear nos bastidores e finalmente bloquear deve ser executado.

Tony Kh
fonte
1

No exemplo que você forneceu, há um problema; scope.Complete()nunca é chamado. Em segundo lugar, não é uma boa prática usar returninstrução dentro de usinginstruções. Consulte o seguinte:

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

Neste exemplo simples, o ponto é isso; o valor descope será nulo quando o uso da instrução for concluído.

Portanto, é melhor não voltar para dentro usando instruções.

daryal
fonte
1
Só porque o 'escopo de retorno' não tem sentido, não significa que a instrução de retorno esteja errada.
Preet Sangha de
só porque não usar as melhores práticas não significa que você fez algo errado. Implica, é melhor evitar, pois pode resultar em consequências imprevistas.
daryal
1
O valor de scopenão será nulo - a única coisa que terá acontecido é que Dispose()terá sido invocado naquela instância e, portanto, a instância não deve ser mais usada (mas não é nulo e não há nada que o impeça de tentar e usar o objeto descartado, mesmo que seja um uso impróprio de um objeto descartável).
Lucero
Lucero está certo. Objetos descartáveis ​​não são nulos após serem descartados. Sua propriedade IsDisposed é true, mas se você verificar em null, obtém false e return scoperetorna uma referência a esse objeto. Dessa forma, se você atribuir essa referência no retorno, você evita que o CG limpe o objeto descartado.
ThunderGr
1

Para certificar-se de que scope.Complete()será chamado, envolva-o com try/finally. O disposeé chamado porque você o envolveu com o usingque é um try/finallybloco alternativo .

using(var scope = new TransactionScope())
{
  try
  {
  // my core logic
  return true; // if condition met else
  return false;
  }
  finally
  {
   scope.Complete();
  }
}
Aristos
fonte
Eu acho que você quer dizer Se você ganhou - Se você quiser, não vou ... de acordo com o seu código. :)
0

Neste exemplo, scope.Complete () nunca será executado. No entanto, o comando de retorno limpará tudo o que está atribuído na pilha. O GC cuidará de tudo o que não for referenciado. Então, a menos que haja um objeto que não possa ser pego pelo GC, não há problema.

ThunderGr
fonte