retourner au milieu d'un bloc à l'aide

196

Quelque chose comme:

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

Je crois que ce n'est pas un endroit approprié pour une déclaration de retour, n'est-ce pas?

tafa
la source

Réponses:

194

Comme plusieurs autres l'ont souligné en général, ce n'est pas un problème.

Le seul cas où cela vous posera des problèmes est si vous revenez au milieu d'une instruction using et que vous retournez également la variable in using. Mais là encore, cela vous causerait également des problèmes même si vous ne retourniez pas et gardiez simplement une référence à une variable.

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

Tout aussi mauvais

Something y;
using ( var x = new Something() ) {
  y = x;
}
JaredPar
la source
1
J'étais sur le point de modifier ma question sur le point que vous avez mentionné. Merci.
tafa
Veuillez m'aider à comprendre pourquoi c'est mauvais. Je voudrais renvoyer un Stream que j'utilise dans une fonction d'aide à une autre fonction pour le traitement d'image. Il semble que le Stream sera supprimé si je le fais?
John Shedletsky
3
@JohnShedletsky Dans ce cas, votre appel de fonction doit être encapsulé avec using. Comme utiliser (Stream x = FuncToReturnStream ()) {...} et ne pas utiliser dans FuncToReturnStream.
Felix Keil
@JohnShedletsky Je suis sûr que c'est parce que l' returninstruction rend la fin du usingbloc inaccessible par tous les chemins de code. La fin du usingbloc doit être exécutée afin que l'objet puisse être supprimé si nécessaire.
facepalm42
147

C'est parfaitement bien.

Vous pensez apparemment que

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

se traduit aveuglément en:

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

Ce qui, certes, serait un problème, et rendrait la usingdéclaration plutôt inutile - c'est pourquoi ce n'est pas ce qu'il fait.

Le compilateur s'assure que l'objet est supprimé avant que le contrôle ne quitte le bloc, quelle que soit la façon dont il quitte le bloc.

James Curran
la source
7
J'étais, apparemment.
tafa
Grande réponse @James Curran! Mais cela me rend plutôt curieux de savoir en quoi cela se traduit. Ou est-ce uniquement exprimable en IL? (que je n'ai jamais vraiment essayé de lire auparavant).
Bart
1
@Bart - Je pense que cela évalue l'expression de retour dans une variable temporaire, puis fait la disposition, puis retourne la variable temporaire.
ToolmakerSteve
@James Curran. De haut en bas, vous seul avez expliqué ce qui s'est passé en arrière-plan. Merci beaucoup.
Sercan Timoçin
@Bart il est probablement traduit par: essayez {... votre code ...} enfin {x.Dispose (); }
Bip901
94

C'est très bien - pas de problème du tout. Pourquoi croyez-vous que c'est mal?

Une instruction using n'est que du sucre syntaxique pour un bloc try / finally, et comme le dit Grzenio, il est également possible de revenir d'un bloc try.

L'expression de retour sera évaluée, puis le bloc finally sera exécuté, puis la méthode reviendra.

Jon Skeet
la source
5
La réponse de James Curran explique ce que je pensais.
tafa
27

Cela fonctionnera parfaitement bien, tout comme le retour au milieu de try{}finally{}

Grzenio
la source
18

C'est tout à fait acceptable. Une utilisation instruction garantit que l'objet IDisposable sera supprimé quoi qu'il arrive.

Depuis MSDN :

L'instruction using garantit que Dispose est appelé même si une exception se produit pendant que vous appelez des méthodes sur l'objet. Vous pouvez obtenir le même résultat en plaçant l'objet dans un bloc try, puis en appelant Dispose dans un bloc finally; en fait, c'est ainsi que l'instruction using est traduite par le compilateur.

mbillard
la source
14

Le code ci-dessous montre comment usingfonctionne:

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

Production:

't1' est créé.
't2' est créé.
't3' est créé.
'Créé à partir de t1, t2, t3' est créé.
«t3» est supprimé.
't2' est supprimé.
«t1» est supprimé.

Les éliminés sont appelés après l'instruction return mais avant la sortie de la fonction.

Bertrand
la source
1
Veuillez noter que certains objets C # se débarrassent d'une manière personnalisée, par exemple, les clients WCF est une instruction using comme ci-dessus retour "ne peut pas accéder à l'objet supprimé"
OzBob
-4

Ce n'est peut-être pas vrai à 100% que c'est acceptable ...

S'il vous arrive d'imbriquer des utilisations et de revenir de l'intérieur d'une imbriquée, cela peut ne pas être sûr.

Prenez ceci comme exemple:

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

Je passais dans un DataTable pour être sorti en csv. Avec le retour au milieu, il écrivait toutes les lignes dans le flux, mais le csv en sortie manquait toujours une ligne (ou plusieurs, selon la taille du tampon). Cela m'a dit que quelque chose n'était pas fermé correctement.

La bonne façon est de vous assurer que toutes les utilisations précédentes sont éliminées correctement:

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();
}
Oui en effet
la source