powrót w środku używanego bloku

196

Coś jak:

using (IDisposable disposable = GetSomeDisposable())
{
    //.....
    //......
    return Stg();
}

Uważam, że nie jest to właściwe miejsce na oświadczenie zwrotne, prawda?

tafa
źródło

Odpowiedzi:

194

Jak kilka innych ogólnie wskazało, nie stanowi to problemu.

Jedynym przypadkiem, który spowoduje problemy, jest powrót w środku instrukcji using i dodatkowo zwrócenie zmiennej using. Ale z drugiej strony spowoduje to również problemy, nawet jeśli nie wrócisz i po prostu zachowasz odwołanie do zmiennej.

using ( var x = new Something() ) { 
  // not a good idea
  return x;
}

Tak samo źle

Something y;
using ( var x = new Something() ) {
  y = x;
}
JaredPar
źródło
1
Właśnie miałem edytować moje pytanie dotyczące wspomnianego punktu. Dzięki.
tafa
Pomóż mi zrozumieć, dlaczego to jest złe. Chciałbym zwrócić Strumień, którego używam w funkcji pomocniczej, do innej funkcji do przetwarzania obrazu. Wygląda na to, że jeśli to zrobię, strumień zostanie usunięty?
John Shedletsky,
3
@JohnShedletsky W takim przypadku wywołanie funkcji powinno być zakończone za pomocą. Jak przy użyciu (Stream x = FuncToReturnStream ()) {...} i bez używania wewnątrz FuncToReturnStream.
Felix Keil
@JohnShedletsky Jestem pewien, że dzieje się tak, ponieważ returninstrukcja powoduje, że koniec usingbloku jest niedostępny dla dowolnej ścieżki kodu. Koniec usingbloku musi zostać uruchomiony, aby obiekt mógł zostać usunięty w razie potrzeby.
facepalm42
147

Jest całkowicie w porządku.

Najwyraźniej tak myślisz

using (IDisposable disposable = GetSomeDisposable())
{
    //.....
    //......
    return Stg();
}

jest ślepo tłumaczone na:

IDisposable disposable = GetSomeDisposable()
//.....
//......
return Stg();
disposable.Dispose();

Co, co prawda, stanowiłoby problem i uczyniłoby to usingstwierdzenie raczej bezcelowym - dlatego nie robi tego.

Kompilator upewnia się, że obiekt jest usuwany, zanim formant opuści blok - niezależnie od tego, jak opuści blok.

James Curran
źródło
7
Najwyraźniej byłem.
tafa
Świetna odpowiedź @James Curran! Ale to mnie ciekawi, na co JEST przetłumaczone. Czy może to tylko wyrazić w IL? (którego tak naprawdę nigdy wcześniej nie próbowałam czytać).
Bart
1
@Bart - myślę o tym jako o ocenie wyrażenia zwrotnego na zmienną tymczasową, a następnie o wykonaniu dyspozycji, a następnie zwróceniu zmiennej tymczasowej.
ToolmakerSteve
@James Curran. Od góry do tutaj, tylko ty wyjaśniłeś, co działo się w tle. Wielkie dzięki.
Sercan Timoçin,
@Bart Prawdopodobnie jest to przetłumaczone na: spróbuj {... kod ...} wreszcie {x.Dispose (); }
Bip901
94

Jest absolutnie w porządku - nie ma problemu. Dlaczego uważasz, że to źle?

Instrukcja using to po prostu cukier składniowy dla bloku try / last, a jak mówi Grzenio, powrót z bloku try może być w porządku.

Wyrażenie zwrotne zostanie ocenione, następnie zostanie wykonany blok w końcu, a następnie metoda powróci.

Jon Skeet
źródło
5
Odpowiedź Jamesa Currana wyjaśnia, o czym myślałem.
tafa
27

Będzie to działać idealnie, tak samo jak powrót w połowie try{}finally{}

Grzenio
źródło
18

To jest całkowicie do przyjęcia. Użyciu oświadczenie zapewnia, że IDisposable obiekt zostanie umieszczony nie wiem co.

Z MSDN :

Instrukcja using zapewnia, że ​​wywoływana jest funkcja Dispose, nawet jeśli wystąpi wyjątek podczas wywoływania metod na obiekcie. Możesz osiągnąć ten sam wynik, umieszczając obiekt w bloku try, a następnie wywołując Dispose w bloku wreszcie; w rzeczywistości tak kompilator tłumaczy instrukcję using.

Mbillard
źródło
14

Poniższy kod pokazuje, jak usingdziała:

private class TestClass : IDisposable
{
   private readonly string id;

   public TestClass(string id)
   {
      Console.WriteLine("'{0}' is created.", id);
      this.id = id;
   }

   public void Dispose()
   {
      Console.WriteLine("'{0}' is disposed.", id);
   }

   public override string ToString()
   {
      return id;
   }
}

private static TestClass TestUsingClose()
{
   using (var t1 = new TestClass("t1"))
   {
      using (var t2 = new TestClass("t2"))
      {
         using (var t3 = new TestClass("t3"))
         {
            return new TestClass(String.Format("Created from {0}, {1}, {2}", t1, t2, t3));
         }
      }
   }
}

[TestMethod]
public void Test()
{
   Assert.AreEqual("Created from t1, t2, t3", TestUsingClose().ToString());
}

Wynik:

Utworzono „t1”.
Utworzono „t2”.
Utworzono „t3”.
Utworzono „Utworzono z t1, t2, t3”.
„t3” jest usuwane.
„t2” jest usuwane.
„t1” jest usuwane.

Pozbywane są wywoływane po instrukcji return, ale przed wyjściem z funkcji.

Bertrand
źródło
1
Należy pamiętać, że niektóre obiekty C # są usuwane w niestandardowy sposób, na przykład klienci WCF to instrukcja using, taka jak powyżej, zwracają „nie można uzyskać dostępu do
usuwanego
-4

Być może nie jest w 100% prawdą, że jest to dopuszczalne ...

Jeśli akurat zagnieżdżasz użytki i wracasz z zagnieżdżonego, może to nie być bezpieczne.

Weź to jako przykład:

using (var memoryStream = new MemoryStream())
{
    using (var textwriter = new StreamWriter(memoryStream))
    {
        using (var csv = new CsvWriter(textwriter))
        {
            //..write some stuff to the stream using the CsvWriter
            return memoryStream.ToArray();
        }
    }
}

Podawałem DataTable, która ma być wyprowadzana jako csv. Z powrotem w środku zapisywał wszystkie wiersze w strumieniu, ale w wyjściowym pliku csv zawsze brakowało wiersza (lub wielokrotności, w zależności od wielkości bufora). To powiedziało mi, że coś nie zostało poprawnie zamknięte.

Prawidłowym sposobem jest upewnienie się, że wszystkie poprzednie zastosowania zostały odpowiednio usunięte:

using (var memoryStream = new MemoryStream())
{
    using (var textwriter = new StreamWriter(memoryStream))
    {
        using (var csv = new CsvWriter(textwriter))
        {
            //..write some stuff to the stream using the CsvWriter
        }
    }

    return memoryStream.ToArray();
}
tak, jasne
źródło