È un buon approccio chiamare return inside usando l'istruzione {}?


93

Voglio solo sapere se è sicuro / un buon approccio chiamare returnall'interno di un usingblocco.

Per es.

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

Sappiamo che l'ultima doppietta più riccia dispose()verrà annullata. Ma cosa accadrà nel caso precedente, dal momento che returnsalta il controllo fuori dallo scopo dato (AFAIK) ...

  1. Mi scope.Complete()viene chiamato?
  2. E così per il dispose()metodo dell'ambito .

1
Una volta che l' using{}ambito è finito, gli oggetti rilevanti vengono eliminati, return"interrompono" l'ambito, quindi gli oggetti verranno eliminati come previsto
Shai

4
Tieni presente che la tua scope.Complete()chiamata non verrà mai soddisfatta con il campione fornito, quindi la transazione verrà sempre ripristinata.
Andy

Indipendentemente dal fatto usingche dispose()venga chiamato 's , quando si ritorna, la funzione contenente questo usingblocco sarà ritornata e tutto ciò che appartiene ad esso sarà orfano. Quindi anche se scopenon fosse stato smaltito "dal using" (sarà, come altri hanno spiegato) verrà comunque smaltito perché la funzione è terminata. Se C # avesse una gotodichiarazione -hai finito di ridere? bene- allora invece di tornare puoi gotodopo la parentesi graffa di chiusura, senza tornare. Logicamente, scopesarebbe ancora disponibile, ma hai appena inserito gotoC #, quindi chi se ne frega della logica in quella fase.
Superbest

C # ha goto .
Jake T.

Risposte:


146

È perfettamente sicuro chiamare returnall'interno del tuo usingblocco, poiché un blocco using è solo un try/finallyblocco.

Nell'esempio precedente dopo la restituzione true, l'ambito verrà eliminato e il valore restituito. return falsee nonscope.Complete() verrà chiamato. tuttavia verrà chiamato a prescindere poiché risiede all'interno del blocco finalmente.Dispose

Il tuo codice è essenzialmente lo stesso di questo (se questo lo rende più facile da capire):

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

Si prega di essere consapevoli del fatto che la transazione non verrà mai impegnata in quanto non è possibile ottenere il scope.Complete()commit della transazione.


13
Dovresti chiarire che Dispose verrà chiamato. Se l'OP non sa cosa succede using, è probabile che non sappia cosa succede finally.
Konrad Rudolph

Va bene lasciare un blocco using con return, ma in caso di TransactionScope potresti avere problemi con l'istruzione using stessa: blogs.msdn.com/b/florinlazar/archive/2008/05/05/8459994.aspx
thewhiteambit

Nella mia esperienza, questo non funziona con gli assembly CLR di SQL Server. Quando è necessario restituire i risultati per una UDF contenente un campo SqlXml che fa riferimento a un oggetto MemoryStream. Ottengo " Impossibile accedere a un oggetto eliminato " e " Tentativo non valido di chiamare Read quando il flusso è chiuso. ", Quindi sono FORZATO a scrivere codice che perde e rinuncia all'istruzione using in questo scenario. :( La mia unica speranza è che SQL CLR gestirà lo smaltimento di questi oggetti. È uno scenario unico, ma ho pensato di condividerlo.
MikeTeeVee

1
@MikeTeeVee - Le soluzioni più pulite sono (a) se il chiamante fa il using, ad esempio using (var callersVar = MyFunc(..)) .., invece di usare all'interno di "MyFunc" - Voglio dire che al chiamante viene fornito lo stream ed è responsabile della sua chiusura tramite usingo esplicitamente, o (b) fai in modo che MyFunc estragga tutte le informazioni necessarie in altri oggetti, che possono essere restituite in sicurezza - quindi gli oggetti dati sottostanti o il flusso possono essere eliminati dal tuo using. Non dovresti scrivere codice che perde.
ToolmakerSteve

7

Va bene - le finallyclausole (che è ciò che fa la parentesi graffa di chiusura della usingclausola sotto il cofano) vengono sempre eseguite quando l'ambito viene lasciato, non importa come.

Tuttavia, questo è vero solo per le istruzioni che si trovano nel blocco finalmente (che non può essere impostato esplicitamente durante l'uso using). Pertanto, nel tuo esempio, scope.Complete()non verrebbe mai chiamato (mi aspetto che il compilatore ti avverta del codice non raggiungibile).


2

In generale, è un buon approccio. Ma nel tuo caso, se torni prima di chiamare scope.Complete(), verrà semplicemente cestinato il TransactionScope. Dipende dal tuo design.

Quindi, in questo esempio, Complete () non viene chiamato e l'ambito viene eliminato, supponendo che erediti l'interfaccia IDisposable.


Deve implementare IDisposable o non verrà compilato.
Chriseyre 2000 il

2

Scope.Complete dovrebbe sicuramente essere chiamato prima return. Il compilatore visualizzerà un avviso e questo codice non verrà mai chiamato.

Riguardo a returnse stesso - sì, è sicuro chiamarlo usingdichiarazione interna . L'utilizzo è tradotto per provare finalmente a bloccare dietro le quinte e infine il blocco deve essere sicuramente eseguito.


1

Nell'esempio fornito, c'è un problema; scope.Complete()non viene mai chiamato. In secondo luogo, non è una buona pratica utilizzare returnistruzioni all'interno di usingdichiarazioni. Fare riferimento a quanto segue:

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

In questo semplice esempio, il punto è quello; il valore di scopesarà null quando l'istruzione using è terminata.

Quindi è meglio non tornare dentro usando le istruzioni.


1
Solo perché "return scope" è inutile, non implica che l'istruzione return sia sbagliata.
Preet Sangha

solo perché non utilizzare le migliori pratiche non implica che hai fatto qualcosa di sbagliato. Ciò implica, è meglio evitare, poiché potrebbe comportare conseguenze impreviste.
daryal

1
Il valore di scopenon sarà nullo - l'unica cosa che sarà accaduta è che Dispose()sarà stata invocata su quell'istanza, e quindi l'istanza non dovrebbe più essere usata (ma non è nullo e non c'è niente che ti impedisce di provare e usare l'oggetto smaltito, anche se questo è effettivamente un uso inappropriato di un oggetto usa e getta).
Lucero

Lucero ha ragione. Gli oggetti usa e getta non sono nulli dopo essere stati eliminati. La sua proprietà IsDisposed è vero, ma se controlli contro nullo si ottiene falso e return scoperestituisce un riferimento a quella oggetto. In questo modo, se si assegna tale riferimento alla restituzione, si impedisce al GC di ripulire l'oggetto smaltito.
ThunderGr

1

Per assicurarti che scope.Complete()venga chiamato, avvolgilo con try/finally. Il disposesi chiama perché lo hai avvolto con il usingche è un try/finallyblocco alternativo .

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

Penso che tu voglia dire Se hai vinto - Se vuoi, non lo farai ... come da codice. :)

0

In questo esempio, scope.Complete () non verrà mai eseguito. Tuttavia, il comando di ritorno pulirà tutto ciò che è assegnato nello stack. Il GC si prenderà cura di tutto ciò che non è referenziato. Quindi, a meno che non ci sia un oggetto che non può essere raccolto dal GC, non ci sono problemi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.