Est-ce une bonne approche d'appeler return à l'intérieur en utilisant l'instruction {}?

90

Je veux juste savoir est-ce une approche sûre / bonne pour appeler à l' returnintérieur d'un usingbloc.

Par ex.

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

Nous savons que la dernière accolade la plus bouclée dispose()sera annulée . Mais que sera-t-il dans le cas ci-dessus, puisque returnsaute le contrôle hors de la portée donnée (AFAIK) ...

  1. Mon scope.Complete()être appelé?
  2. Et il en va de même pour la dispose()méthode du scope .
Paolo Moretti
la source
1
Une fois la using{}portée terminée, les objets concernés sont éliminés, return"briseront" la portée - de sorte que les objets seront disposés comme prévu
Shai
2
Sachez que votre scope.Complete()appel ne sera jamais atteint avec l'échantillon que vous avez fourni, de sorte que votre transaction sera toujours annulée.
Andy
Indépendamment du fait que usings dispose()soit appelé, lorsque vous revenez, la fonction contenant ce usingbloc sera retournée et tout ce qui lui appartient sera orphelin. Ainsi, même si elle scopen'avait pas été éliminée "par le using" (ce sera, comme d'autres l'ont expliqué), elle le sera quand même car la fonction s'est terminée. Si C # avait une gotodéclaration, avez-vous fini de rire? bon- alors au lieu de revenir, vous pourriez gotoaprès l'accolade de fermeture, sans revenir. Logiquement, scopeserait toujours disposé, mais vous venez de mettre gotoen C # alors qui se soucie de la logique à ce stade.
Superbe
C # a obtenu .
Jake T.

Réponses:

142

Il est parfaitement sûr d'appeler à l' returnintérieur de votre usingbloc, car un bloc using n'est qu'un try/finallybloc.

Dans votre exemple ci-dessus après le retour true, la portée sera supprimée et la valeur retournée. return false, et nescope.Complete() sera pas appelé. Disposecependant sera appelé indépendamment car il réside dans le bloc finally.

Votre code est essentiellement le même (si cela facilite la compréhension):

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

Veuillez noter que votre transaction ne sera jamais validée car il n'y a aucun moyen scope.Complete()de valider la transaction.

Øyvind Bråthen
la source
13
Vous devez préciser que vous Dispose serez appelé. Si le PO ne sait pas ce qui se passe using, il est probable qu'il ne sache pas ce qui se passe finally.
Konrad Rudolph
Vous pouvez laisser un bloc using avec return, mais dans le cas de TransactionScope, vous pourriez avoir des problèmes avec l'instruction using elle-même: blogs.msdn.com/b/florinlazar/archive/2008/05/05/8459994.aspx
thewhiteambit
D'après mon expérience, cela ne fonctionne pas avec les assemblys SQL Server CLR. Lorsque j'ai besoin de renvoyer des résultats pour un UDF contenant un champ SqlXml qui fait référence à un objet MemoryStream. J'obtiens " Impossible d'accéder à un objet supprimé " et " Tentative non valide d'appeler Read lorsque le flux est fermé. ", Je suis donc FORCÉ d'écrire du code qui fuit et de renoncer à l'instruction using dans ce scénario. :( Mon seul espoir est que le SQL CLR gérera l'élimination de ces objets. C'est un scénario unique, mais je pensais que je partagerais.
MikeTeeVee
1
@MikeTeeVee - Les solutions plus propres sont soit (a) demander à l'appelant de faire le using, par exemple using (var callersVar = MyFunc(..)) .., au lieu d'avoir l'utilisation à l'intérieur de "MyFunc" - je veux dire que l'appelant reçoit le flux et est responsable de le fermer via usingou explicitement, ou (b) Demandez à MyFunc d' extraire toutes les informations nécessaires dans d'autres objets, qui peuvent être transmises en toute sécurité - alors les objets de données sous-jacents ou le flux peuvent être supprimés par votre using. Vous ne devriez pas avoir à écrire du code qui fuit.
ToolmakerSteve
6

C'est très bien - les finallyclauses (c'est ce que fait l'accolade fermante de la usingclause sous le capot) sont toujours exécutées lorsque la portée est laissée, peu importe comment.

Cependant, cela n'est vrai que pour les instructions qui se trouvent dans le bloc finally (qui ne peut pas être défini explicitement lors de l'utilisation using). Par conséquent, dans votre exemple, scope.Complete()ne serait jamais appelé (je m'attends à ce que le compilateur vous avertisse du code inaccessible).

Lucero
la source
2

En général, c'est une bonne approche. Mais dans votre cas, si vous revenez avant d'appeler le scope.Complete(), il supprimera simplement le TransactionScope. Dépend de votre conception.

Ainsi, dans cet exemple, Complete () n'est pas appelé et la portée est supprimée, en supposant qu'elle hérite de l'interface IDisposable.

M. Mennan Kara
la source
Il doit implémenter IDisposable ou utiliser wont compile.
Chriseyre2000
2

scope.Complete doit être appelé avant return. Le compilateur affichera un avertissement et ce code ne sera jamais appelé.

En ce qui concerne returnlui-même - oui, il est prudent de l'appeler usingdéclaration intérieure . L'utilisation est traduite en bloc try-finally dans les coulisses et enfin block doit être certainement exécuté.

Tony Kh
la source
1

Dans l'exemple que vous avez fourni, il y a un problème; scope.Complete()n'est jamais appelé. Deuxièmement, ce n'est pas une bonne pratique d'utiliser des returninstructions dans des usinginstructions. Reportez-vous à ce qui suit:

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

Dans cet exemple simple, le point est que; la valeur de scopesera nulle lorsque l'instruction d'utilisation est terminée.

Il vaut donc mieux ne pas retourner à l'intérieur en utilisant des instructions.

daryal
la source
1
Ce n'est pas parce que 'return scope' est inutile, cela n'implique pas que l'instruction return est fausse.
Preet Sangha
simplement parce que ne pas utiliser les meilleures pratiques n'implique pas que vous avez fait quelque chose de mal. Cela implique qu'il vaut mieux éviter, car cela peut entraîner des conséquences imprévues.
daryal
1
La valeur de scopene sera pas nulle - la seule chose qui se sera produite est qu'elle Dispose()aura été invoquée sur cette instance, et donc l'instance ne devrait plus être utilisée (mais elle n'est pas nulle et rien ne vous empêche d'essayer d'utiliser l'objet éliminé, même s'il s'agit bien d'une utilisation inappropriée d'un objet jetable).
Lucero
Lucero a tout à fait raison. Les objets jetables ne sont pas nuls après avoir été éliminés. Sa propriété IsDisposed est true, mais si vous vérifiez la valeur null, vous obtenez false et return scoperenvoie une référence à cet objet. De cette façon, si vous attribuez cette référence au retour, vous empêchez le GC de nettoyer l'objet éliminé.
ThunderGr
1

Pour vous assurer que le scope.Complete()sera appelé, enveloppez-le avec try/finally. Le disposeest appelé parce que vous l'avez enveloppé avec le usingqui est un try/finallybloc alternatif .

using(var scope = new TransactionScope())
{
  try
  {
  // my core logic
  return true; // if condition met else
  return false;
  }
  finally
  {
   scope.Complete();
  }
}
Aristos
la source
Je pense que tu veux dire si tu as gagné - Si tu veux, non pas ... selon ton code. :)
0

Dans cet exemple, scope.Complete () ne s'exécutera jamais. Cependant, la commande return nettoiera tout ce qui est assigné sur la pile. Le GC s'occupera de tout ce qui n'est pas référencé. Donc, à moins qu'il y ait un objet qui ne puisse pas être capté par le GC, il n'y a pas de problème.

ThunderGr
la source