Czy dobrym podejściem jest wywołanie funkcji return inside przy użyciu instrukcji {}?

93

Chcę tylko wiedzieć, czy dzwonienie returnwewnątrz usingbloku jest bezpieczne / dobre .

Na przykład.

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

Wiemy, że w końcu najbardziej zakręcony aparat dispose()zostanie odwołany. Ale co będzie w powyższym przypadku, skoro returnwyskakuje sterowanie poza podany zakres (AFAIK) ...

  1. Czy moje scope.Complete()wezwanie?
  2. I tak w przypadku metody lunety dispose().
Paolo Moretti
źródło
1
Gdy using{}zakres się skończy, odpowiednie obiekty zostaną usunięte, return„zepsują” zasięg - więc obiekty zostaną usunięte zgodnie z oczekiwaniami
Shai
4
Pamiętaj, że Twoje scope.Complete()połączenie nigdy nie zostanie odebrane przy użyciu dostarczonej próbki, więc transakcja zawsze zostanie wycofana.
Andy
Bez względu na to, czy zostanie wywołana usings dispose(), po powrocie funkcja zawierająca ten usingblok zostanie zwrócona i wszystko, co do niej należy, zostanie osierocone. Więc nawet jeśli scopenie został usunięty „przez using” (będzie, jak wyjaśniali inni), i tak zostanie usunięty, ponieważ funkcja zakończyła się. Gdyby C # miał gotostwierdzenie - czy skończyłeś się już śmiać? dobrze - wtedy zamiast powrotu można gotoby po nawiasie zamykającym, bez powrotu. Logicznie rzecz biorąc, scopenadal byłby usunięty, ale właśnie umieściłeś gotoC #, więc kogo obchodzi logika na tym etapie.
Superbest
C # goto .
Jake T.

Odpowiedzi:

146

Wykonywanie połączeń returnwewnątrz usingbloku jest całkowicie bezpieczne , ponieważ blok using to tylko try/finallyblok.

W powyższym przykładzie po powrocie truezakres zostanie usunięty, a wartość zwrócona. return falseI scope.Complete()będzie nie sprawdzony. Disposejednak zostanie wywołany niezależnie, ponieważ znajduje się wewnątrz ostatniego bloku.

Twój kod jest zasadniczo taki sam jak ten (jeśli to ułatwia zrozumienie):

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

Należy pamiętać, że transakcja nigdy nie zostanie zatwierdzona, ponieważ nie ma sposobu, aby scope.Complete()ją zatwierdzić.

Øyvind Bråthen
źródło
13
Powinieneś wyjaśnić, że Dispose zostaniesz wezwany. Jeśli OP nie wie, co się dzieje using, prawdopodobnie nie wie, co się dzieje finally.
Konrad Rudolph
Można zostawić blok using z powrotem, ale w przypadku TransactionScope możesz mieć problemy z samą instrukcją using
thewhiteambit
Z mojego doświadczenia wynika, że ​​nie działa to w przypadku zestawów CLR programu SQL Server. Kiedy muszę zwrócić wyniki dla UDF zawierającego pole SqlXml, które odwołuje się do obiektu MemoryStream. Otrzymuję komunikat „ Nie można uzyskać dostępu do usuniętego obiektu ” i „ Nieprawidłowa próba wywołania Read, gdy strumień jest zamknięty. ”, Więc jestem ZMUSZONY do pisania nieszczelnego kodu i rezygnacji z instrukcji using w tym scenariuszu. :( Moją jedyną nadzieją jest to, że SQL CLR poradzi sobie z pozbyciem się tych obiektów. To wyjątkowy scenariusz, ale pomyślałem, że się nim podzielę.
MikeTeeVee
1
@MikeTeeVee - Rozwiązania czyszczącej obejmują (a) mają abonenta należy wykonać using, na przykład using (var callersVar = MyFunc(..)) .., zamiast posiadania using wewnątrz „myFunc” - oznacza, że rozmówca otrzymuje strumień i jest odpowiedzialny za zamykania poprzez usinglub bezpośrednio, lub (b) niech MyFunc wyodrębni wszystkie potrzebne informacje do innych obiektów, które można bezpiecznie przekazać z powrotem - wtedy bazowe obiekty danych lub strumień mogą zostać usunięte przez using. Nie powinieneś pisać nieszczelnego kodu.
ToolmakerSteve
7

W porządku - finallyklauzule (co robi zamykający nawias klamrowy usingklauzuli pod maską) zawsze są wykonywane po opuszczeniu zakresu, bez względu na sposób.

Jest to jednak prawdziwe tylko dla instrukcji, które znajdują się w ostatnim bloku (którego nie można jawnie ustawić podczas używania using). Dlatego w twoim przykładzie scope.Complete()nigdy nie zostałoby wywołane (oczekuję jednak, że kompilator ostrzeże Cię o nieosiągalnym kodzie).

Lucero
źródło
2

Ogólnie jest to dobre podejście. Ale w twoim przypadku, jeśli wrócisz przed wywołaniem scope.Complete(), po prostu usunie to TransactionScope. Zależy od twojego projektu.

Tak więc w tym przykładzie Complete () nie jest wywoływana, a zakres jest usuwany, przy założeniu, że dziedziczy on interfejs IDisposable.

M. Mennan Kara
źródło
Musi implementować IDisposable lub używać nie kompilować.
Chriseyre2000
2

scope.Complete należy zdecydowanie wywołać wcześniej return. Kompilator wyświetli ostrzeżenie i ten kod nigdy nie zostanie wywołany.

Jeśli chodzi o returnsiebie - tak, można to bezpiecznie nazwać usingoświadczeniem wewnętrznym . Użycie jest tłumaczone na blok try-final za sceną i ostatecznie blok ma być z pewnością wykonany.

Tony Kh
źródło
1

W podanym przykładzie występuje problem; scope.Complete()nigdy nie jest wezwany. Po drugie, nie jest dobrą praktyką stosowanie returninstrukcji wewnątrz usinginstrukcji. Zapoznaj się z następującymi informacjami:

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

W tym prostym przykładzie chodzi o to; wartośćscope will będzie zerowa po zakończeniu używania instrukcji.

Dlatego lepiej nie wracać do środka za pomocą instrukcji.

daryal
źródło
1
Tylko dlatego, że „zakres zwrotu” nie ma sensu, nie oznacza to, że instrukcja return jest błędna.
Preet Sangha
tylko dlatego, że niestosowanie najlepszych praktyk nie oznacza, że ​​zrobiłeś coś złego. Oznacza to, że najlepiej tego unikać, ponieważ może to skutkować nieprzewidzianymi konsekwencjami.
daryal
1
Wartość scopewill nie będzie zerowa - jedyną rzeczą, która się wydarzy, jest to, że Dispose()zostanie wywołana w tej instancji, dlatego instancja nie powinna być już używana (ale nie jest pusta i nic nie stoi na przeszkodzie, aby spróbować użyć usuwany przedmiot, nawet jeśli rzeczywiście jest to niewłaściwe użycie przedmiotu jednorazowego użytku).
Lucero
Lucero ma całkowitą rację. Obiekty jednorazowe nie mają wartości NULL po usunięciu. Jego właściwość IsDisposed ma wartość true, ale jeśli sprawdzisz wartość null, otrzymasz false i zwrócisz return scopeodwołanie do tego obiektu. W ten sposób, jeśli przypiszesz to odwołanie po powrocie, zapobiegniesz czyszczeniu usuniętego obiektu przez GC.
ThunderGr,
1

Aby upewnić się, że scope.Complete()zostanie wywołana, owiń ją try/finally. disposeNazywa bo trzeba zawinąć go z usingktóry jest alternatywą try/finallyblok.

using(var scope = new TransactionScope())
{
  try
  {
  // my core logic
  return true; // if condition met else
  return false;
  }
  finally
  {
   scope.Complete();
  }
}
Aristos
źródło
Myślę, że chcesz powiedzieć, że jeśli wygrałeś - jeśli chcesz, nie ... zgodnie z Twoim kodem. :)
0

W tym przykładzie scope.Complete () nigdy nie zostanie wykonana. Jednak polecenie return wyczyści wszystko, co jest przypisane na stosie. GC zajmie się wszystkim, do czego nie ma odniesienia. Więc jeśli nie ma przedmiotu, którego GC nie może odebrać, nie ma problemu.

ThunderGr
źródło