Co się stanie, jeśli wrócę przed zakończeniem korzystania z instrukcji? Czy rozdysponowanie zostanie wywołane?

115

Mam następujący kod

using(MemoryStream ms = new MemoryStream())
{
     //code
     return 0;
}

dispose()Metoda jest wywoływana na końcu usingzestawienia szelki }prawda? Skoro ja returnprzed końcem usingoświadczenia, czy MemoryStreamprzedmiot zostanie zutylizowany prawidłowo? co się tutaj stało?

NLV
źródło
4
@JonH: Znajdź dokładny duplikat, a następnie zagłosuj, aby zamknąć w takim przypadku.
Noldorin
@Noldorin: Poszedłem szuka dupe na ten temat, bo pomyślałem, że musi być poproszony wcześniej, ale nie udało mi się znaleźć. Myślę, że wciąż są tam łatwe pytania. :)
Randolpho
@JonH i @Noldorin - duplikaty zostałyby przedstawione podczas tworzenia pytania, wyszukuje „podobne pytania”, funkcja, której ludzie wydają się nie używać w wystarczającym stopniu.
Adam Houldsworth,
@Adam: spróbuj sam. Skopiuj / wklej tytuł i zobacz, jakie duplikaty są prezentowane przez system. Dam ci wskazówkę: odpowiedź brzmi: żadna. Podobnie, jeśli wyszukujesz przez wyszukiwarkę Google lub SO. Wygląda na to, że to pytanie nie zostało wcześniej zadane.
Randolpho
Aaap ... cofam to. Właśnie znalazłem prawie duplikat, po kilku bardzo zaangażowanych poszukiwaniach: stackoverflow.com/questions/2641692/… Teraz pytanie jest zadawane zupełnie inaczej, ale ostateczne pytanie jest prawie takie samo. Myślę, że mimo wszystko możemy uznać to za oszustwo.
Randolpho

Odpowiedzi:

167

Tak, Disposezostanie wezwany. Jest wywoływana, gdy tylko wykonanie opuści zakres usingbloku, niezależnie od tego, jakie środki zajęło opuszczenie bloku, czy to koniec wykonywania bloku, returninstrukcja czy wyjątek.

Jak słusznie zauważa @Noldorin, użycie usingbloku w kodzie zostaje wkompilowane do try/ finally, z Disposewywołaniem w finallybloku. Na przykład następujący kod:

using(MemoryStream ms = new MemoryStream())
{
     //code
     return 0;
}

skutecznie staje się:

MemoryStream ms = new MemoryStream();
try
{
    // code
    return 0;
}
finally
{
    ms.Dispose();
}

Tak więc, ponieważ finallyjest gwarantowane wykonanie po tryzakończeniu wykonywania bloku, niezależnie od jego ścieżki wykonania, Disposegwarantuje się wywołanie, bez względu na wszystko.

Aby uzyskać więcej informacji, zobacz ten artykuł MSDN .

Dodatek:
tylko małe zastrzeżenie do dodania: ponieważ Disposegwarantowane jest wywołanie, prawie zawsze dobrym pomysłem jest upewnienie się, że Disposenigdy nie zgłasza wyjątku podczas implementacji IDisposable. Niestety, w podstawowej bibliotece znajdują się klasy, które w pewnych okolicznościach Disposesą wywoływane - patrzę na Ciebie, odwołanie do usługi WCF / serwer proxy! - a kiedy tak się stanie, może być bardzo trudno wyśledzić oryginalny wyjątek, jeśli Disposezostał wywołany podczas rozwijania stosu wyjątków, ponieważ oryginalny wyjątek zostaje połknięty na rzecz nowego wyjątku wygenerowanego przez Disposewywołanie. To może być irytujące. A może to frustrujące i denerwujące? Jeden z dwóch. Może obydwa.

Randolpho
źródło
4
Myślę, że przekonasz się, że jest on skutecznie skompilowany w blok try-final z wywołaniem Disposew końcu, więc skutecznie pracuje nad implementacją finally, jak opisujesz.
Noldorin
@Noldorin: dokładnie. Chociaż przypuszczam, że mógłbym powiedzieć to wprost. Edytuj nadchodzące ....
Randolpho
1
Należy również pamiętać, że istnieją pewne okoliczności, w których nie można zagwarantować wykonania bloku final, na przykład użycie Environment.FailFast i wystąpienie wyjątku StackOverFlowException.
Christopher McAtackney,
@ C.McAtackney: również dobra uwaga. Ponadto IIRC, OutOfMemoryException; w zasadzie jeśli nie możesz złapać wyjątku, ponieważ jest to krytyczna awaria wykonania, Dispose nie zostanie wywołane. Oczywiście w takim przypadku program ma gwarancję awarii wraz z przydzieloną mu pamięcią, więc w 99,9% przypadków nie stanowi to problemu, chyba że robisz niewygodne rzeczy, takie jak zapisywanie do pliku w metodzie usuwania . To znaczy oprócz katastrofalnej awarii programu.
Randolpho
Nigdy nie należy używać instrukcji „using ()” z usługą WCF - zapoznaj się z tym artykułem, aby uzyskać więcej informacji. Oto fragment, którego używam dla serwerów proxy WCF: „WCFProxy variableName = null; spróbuj {nazwa zmiennej = new WCFProxy (); // Tutaj kod TODO variableName.Proxy.Close (); variableName.Dispose (); } catch (Exception) {if (variableName! = null && variableName.Proxy! = null) {variableName.Proxy.Abort (); } rzucać; } '
Dave Black,
18

usinginstrukcje zachowują się dokładnie jak try ... finallybloki, więc zawsze będą wykonywane na dowolnej ścieżce wyjścia kodu. Uważam jednak, że są one przedmiotem bardzo nielicznych i rzadkich sytuacji, w których finallybloki nie są wywoływane. Przykładem, który pamiętam, jest wyjście z wątku pierwszego planu, gdy wątki w tle są aktywne: wszystkie wątki poza GC są wstrzymane, co oznacza, że finallybloki nie są uruchamiane.

Oczywista zmiana: zachowują się tak samo, z wyjątkiem logiki, która pozwala im obsługiwać obiekty IDisposable, d'oh.

Dodatkowa zawartość: można je łączyć (w przypadku różnych typów):

using (SqlConnection conn = new SqlConnection("string"))
using (SqlCommand comm = new SqlCommand("", conn))
{

}

A także rozdzielany przecinkami (gdzie typy są takie same):

using (SqlCommand comm = new SqlCommand("", conn), 
       SqlCommand comm2 = new SqlCommand("", conn))
{

}
Adam Houldsworth
źródło
4

Twój obiekt MemoryStream zostanie prawidłowo usunięty, nie musisz się tym martwić.

Otávio Décio
źródło
0

Po skompilowaniu spójrz na swój kod w reflektorze. Przekonasz się, że kompilator refaktoryzuje kod, aby upewnić się, że metoda dispose jest wywoływana w strumieniu.

Wil P
źródło