2009-12-16 6 views
8

Édition: Deux options illustrées ci-dessous.Quelle est la meilleure façon de renvoyer les IDisposables construits en toute sécurité?

Si vous êtes juste en utilisant la fonctionnalité qu'un IDisposable fournit, la clause bien nommée using fonctionne très bien. Si vous êtes enveloppant un IDisposable dans un objet, l'objet contenant lui-même doit être IDisposable et vous devez implémenter le modèle approprié (soit une classe IDisposable scellée, soit le messier mais standard virtual pattern).

Mais parfois, une méthode d'usine assistée est bonne pour la propreté. Si vous renvoyez un IDisposable directement après la construction, vous allez bien, mais si vous le construisez d'abord puis le modifiez ou exécutez un code qui peut déclencher une exception avant de revenir, vous devez appeler en toute sécurité .Dispose() - mais seulement s'il y avait une erreur.

Par exemple, le code dangereux pourrait ressembler à ceci ...

DbCommand CreateCommandUnsafely(string commandText) 
{ 
    var newCommand = connection.CreateCommand(); 
    newCommand.CommandText = commandText; //what if this throws? 
    return newCommand; 
}  

Solutions Deux variantes sûres suivantes ...

DbCommand CreateCommandSafelyA(string commandText) 
{ 
    DbCommand newCommand = null; 
    bool success = false; 
    try { 
     newCommand = connection.CreateCommand(); 
     newCommand.CommandText = commandText; //if this throws... 
     success=true; 
     return newCommand; 
    } finally{ 
     if (!success && newCommand != null) 
      newCommand.Dispose(); //...we'll clean up here. 
    } 
} 


DbCommand CreateCommandSafelyB(string commandText) 
{ 
    DbCommand newCommand = null; 
    try { 
     newCommand = connection.CreateCommand(); 
     newCommand.CommandText = commandText; //if this throws... 
     return newCommand; 
    } catch { 
     if (newCommand != null) 
      newCommand.Dispose(); //...we'll clean up here. 
     throw; 
    } 
} 

variante A Safe est juste une ligne plus , mais semble être l'approche idiomatique. Il ne semble pas y avoir de solutions vraiment concises, bien que certaines affiches ci-dessous donnent des options utilisant le lambda qui extraient l'encapsulation de cette logique.

Le ballonnement de code avec l'une des méthodes ci-dessus reste sans danger, et est particulièrement aggravante avec le code qui ressemblait à l'origine ...

return new MyDisposableThing { 
    OptionA = "X", 
    OptionB = B.Blabla, 
    Values = src.Values.Where(priority => priority > 1.0), 
}; 

Le code ci-dessus s'écrit est en toute sécurité un peu plus et moins lisible parce que vous ne pouvez plus utiliser en toute sécurité la syntaxe de setter raccourcie.

Répondre

4

Je crois que c'est le modèle standard:

DbCommand CreateCommand(string commandText) 
{ 
    DbCommand newCommand = null; 
    bool success = false; 
    try 
    { 
     newCommand = connection.CreateCommand(); 
     newCommand.CommandText = commandText; 
     success = true; 
     return newCommand; 
    } 
    finally 
    { 
     if (!success & newCommand != null) 
      newCommand.Dispose(); 
    } 
} 

Il ne se coince pas et réémettre l'erreur.

+0

Cela ressemble à du code que je vois ailleurs. Savez-vous pourquoi c'est préférable? –

+1

Attraper des exceptions arbitraires (comme les autres réponses semblent préconiser) provoque une variété de problèmes dans des circonstances particulières, et devrait être évité autant que possible. En plaçant la disposition à l'intérieur d'une instruction 'finally', le modèle standard évite les exceptions, et (lorsque la création de l'objet échoue) il imite le comportement de l'instruction nominale' using'. –

7

Non - Je pense qu'il n'y a pas de meilleur moyen.

Cependant, vous pouvez écrire une classe d'aide: cependant,

return DisposeHelper.DisposeOnError(connection.CreateCommand(), cmd => cmd.CommandText = commandText); 

Je ne sais pas, si cela est vraiment une meilleure façon:

public static class DisposeHelper 
{ 
    public static TDisposable DisposeOnError<TDisposable>(TDisposable dispoable, Action<TDisposable> action) 
    where TDisposable : IDisposable 
    { 
    try 
    { 
     action(dispoable); 
    } 
    catch(Exception) 
    { 
     disposable.Dispose(); 
     throw; 
    } 

    return disposable; 
    } 
} 

Alors vous pourriez écrire.

2

Vous pouvez envisager d'écrire une méthode d'extension :

public static class Disposable 
{ 
    public static void SafelyDo<T>(this T disp, Action<T> action) where T : IDisposable 
    { 
     try 
     { 
      action(disp); 
     } 
     catch 
     { 
      disp.Dispose(); 
      throw; 
     } 
    } 
} 

Cela vous permet d'écrire du code comme ceci:

var disp = new MyDisposable(); 
disp.SafelyDo(d => 
    { 
     d.Foo = "Ploeh"; 
     d.Bar = 42; 
    }); 
return disp; 
0

Je pense que vous la question surcharger.

Si votre méthode renvoie un objet jetable, alors vous dites: «Je renonce par la présente à la propriété de cet objet, pour le meilleur ou pour le pire». Si une erreur se produit pendant que vous le construisez, alors pourquoi cela devrait-il faire la différence? Le code appelant en disposera même si vous lancez une exception.

Par exemple:

DbCommand CreateCommand(string commandText) { 
    var newCommand = connection.CreateCommand(); 
    newCommand.CommandText = commandText; // what if this throws? 
    return newCommand; 
} 

void UseCommand() { 
    using(var cmd = CreateCommand("my query goes here")) { 
     // consume the command 
    } 
} 

Edit: Malheureusement, si une exception est levée à l'intérieur CreateCommand, la variable cmd est jamais définie et l'objet ne sera pas correctement placé.

+3

Je ne crois pas que ce soit vrai. Puisque la variable cmd ne sera jamais affectée, le bloc using n'appellera pas cmd.Dispose(). –

+0

Le problème est le suivant: CommandText signifiera que la commande newCommand n'est jamais renvoyée à l'instruction using, elle ne sera donc pas supprimée. – Kleinux

+0

Bon point, les gars. Dans ce cas, je vote pour quelque chose dans le style de la méthode générique de winSharp93. –

Questions connexes