Rückkehr in die Mitte eines using-Blocks

196

Etwas wie:

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

Ich glaube, es ist kein geeigneter Ort für eine Rückgabeerklärung, oder?

Tafa
quelle

Antworten:

194

Wie mehrere andere allgemein betont haben, ist dies kein Problem.

Der einzige Fall, der Probleme verursacht, ist, wenn Sie in der Mitte einer using-Anweisung und zusätzlich die Variable in using zurückgeben. Andererseits würde dies auch zu Problemen führen, selbst wenn Sie nicht zurückkehren und einfach einen Verweis auf eine Variable behalten.

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

Genauso schlimm

Something y;
using ( var x = new Something() ) {
  y = x;
}
JaredPar
quelle
1
Ich wollte gerade meine Frage zu dem von Ihnen erwähnten Punkt bearbeiten. Vielen Dank.
Tafa
Bitte helfen Sie mir zu verstehen, warum das schlecht ist. Ich möchte einen Stream, den ich in einer Hilfsfunktion verwende, an eine andere Funktion für die Bildverarbeitung zurückgeben. Es scheint, als würde der Stream entsorgt, wenn ich das tue?
John Shedletsky
3
@JohnShedletsky In diesem Fall sollte Ihr Funktionsaufruf mit using umbrochen werden. Wie bei der Verwendung von (Stream x = FuncToReturnStream ()) {...} und nicht bei der Verwendung in FuncToReturnStream.
Felix Keil
@ JohnShedletsky Ich bin sicher, es liegt daran return, dass die Anweisung das Ende des usingBlocks für Codepfade unzugänglich macht . Das Ende des usingBlocks muss ausgeführt werden, damit das Objekt bei Bedarf entsorgt werden kann.
facepalm42
147

Es ist vollkommen in Ordnung.

Sie denken das anscheinend

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

wird blind übersetzt in:

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

Was zugegebenermaßen ein Problem wäre und die usingAussage ziemlich sinnlos machen würde - weshalb dies nicht der Fall ist.

Der Compiler stellt sicher, dass das Objekt entsorgt wird, bevor die Steuerung den Block verlässt - unabhängig davon, wie sie den Block verlässt.

James Curran
quelle
7
Ich war anscheinend.
Tafa
Tolle Antwort @James Curran! Aber es macht mich ziemlich neugierig, in was es übersetzt wird. Oder ist das nur in IL ausdrückbar? (was ich noch nie wirklich versucht habe zu lesen).
Bart
1
@Bart - Ich betrachte es als Auswertung des Rückgabeausdrucks in eine temporäre Variable, dann als Dispose und als Rückgabe der temporären Variablen.
ToolmakerSteve
@ James Curran. Von oben nach hier haben nur Sie erklärt, was im Hintergrund passiert ist. Danke vielmals.
Sercan Timoçin
@Bart es wird wahrscheinlich übersetzt in: versuche {... deinen Code ...} endlich {x.Dispose (); }
Bip901
94

Es ist absolut in Ordnung - überhaupt kein Problem. Warum glaubst du, ist es falsch?

Eine using-Anweisung ist nur syntaktischer Zucker für einen try / finally-Block, und wie Grzenio sagt, ist es in Ordnung, auch von einem try-Block zurückzukehren.

Der Rückgabeausdruck wird ausgewertet, dann wird der finally-Block ausgeführt, und die Methode wird zurückgegeben.

Jon Skeet
quelle
5
James Currans Antwort erklärt, was ich dachte.
Tafa
27

Dies wird einwandfrei funktionieren, genauso wie die Rückkehr in die Mitte von try{}finally{}

Grzenio
quelle
18

Das ist völlig akzeptabel. Eine using- Anweisung stellt sicher, dass das IDisposable-Objekt unabhängig davon entsorgt wird.

Von MSDN :

Die using-Anweisung stellt sicher, dass Dispose aufgerufen wird, auch wenn beim Aufrufen von Methoden für das Objekt eine Ausnahme auftritt. Sie können das gleiche Ergebnis erzielen, indem Sie das Objekt in einen try-Block einfügen und dann Dispose in einem finally-Block aufrufen. Auf diese Weise wird die using-Anweisung vom Compiler übersetzt.

mbillard
quelle
14

Der folgende Code zeigt, wie usinges funktioniert:

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

Ausgabe:

't1' wird erstellt.
't2' wird erstellt.
't3' wird erstellt.
'Erstellt aus t1, t2, t3' wird erstellt.
't3' ist entsorgt.
't2' ist entsorgt.
't1' ist entsorgt.

Die disposed werden nach der return-Anweisung, jedoch vor dem Beenden der Funktion aufgerufen.

Bertrand
quelle
1
Bitte beachten Sie, dass einige C # -Objekte auf benutzerdefinierte Weise entsorgt werden. Beispielsweise handelt es sich bei WCF-Clients um eine using-Anweisung wie oben. Return "kann nicht auf entsorgte Objekte zugreifen"
OzBob
-4

Vielleicht ist es nicht 100% wahr, dass dies akzeptabel ist ...

Wenn Sie zufällig Benutzer verschachteln und aus einem verschachtelten zurückkehren, ist dies möglicherweise nicht sicher.

Nehmen Sie dies als Beispiel:

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

Ich habe eine DataTable übergeben, die als CSV ausgegeben werden soll. Mit der Rückgabe in der Mitte wurden alle Zeilen in den Stream geschrieben, aber der ausgegebenen CSV fehlte immer eine Zeile (oder mehrere, abhängig von der Größe des Puffers). Dies sagte mir, dass etwas nicht richtig geschlossen wurde.

Der richtige Weg ist, sicherzustellen, dass alle vorherigen Verwendungen ordnungsgemäß entsorgt werden:

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();
}
ja, genau
quelle