2009-12-16 11 views
8

Editar: dos opciones que se muestran a continuación.¿Cuál es la mejor manera de volver IDisposables construidos de manera segura?

Si solo tiene usando la funcionalidad que proporciona un IDisposable, la cláusula using acertadamente funciona bien. Si está envolviendo un IDisposable en un objeto, el objeto que lo contiene debe ser IDisposable y debe implementar el patrón apropiado (ya sea una clase sellada IDisposable, o la más desordenada pero standard virtual pattern).

Pero a veces un método de fábrica ayudante es bueno para la limpieza. Si devuelve un IDisposable directamente después de la construcción, que está bien, pero si primero se construye y luego modificarlo o no ejecutar el código que puede lanzar una excepción antes de regresar, es necesario llamar a seguridad .Dispose() - pero solamente si había un error.

Por ejemplo, el código inseguro podría tener este aspecto ...

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

Soluciones dos variantes seguras sigue ...

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

Segura variante A es sólo una línea más larga , pero parece ser el enfoque idiomático. No parece que haya ninguna solución muy concisos, aunque algunos carteles dan a continuación algunas opciones lambda-que extraen mediante el encapsulado de esta lógica.

La hinchazón de código con cualquiera de los métodos anteriormente seguras sigue siendo, y es especialmente agravante con código que inicialmente parecía ...

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

El código anterior se escribe de forma segura es un poco más largo y menos legible porque ya no puede usar con seguridad la sintaxis del organizador acortado.

Respuesta

4

Creo que este es el patrón estándar:

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

No coger y volver a generar el error.

+0

Parece el código que veo en otra parte. ¿Sabes por qué es esto preferible? –

+1

La captura de excepciones arbitrarias (como las otras respuestas parecen abogar) causa una variedad de problemas en circunstancias especiales, y debe evitarse siempre que sea posible. Al colocar la eliminación dentro de una declaración 'finally', el patrón estándar evita capturar excepciones, y (cuando la creación del objeto falla) imita el comportamiento de la instrucción' using' nominal. –

7

n - Creo que no hay una mejor manera.

Sin embargo, se podría escribir una clase de ayuda:

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

por lo que podría escribir:

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

No estoy seguro, sin embargo, si esto es realmente una mejor manera.

2

Usted podría considerar escribir un método de extensión :

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

Esto permitirá escribir código como este:

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

Creo que está complicar el asunto.

Si su método devuelve un objeto desechable, significa "Por la presente renuncio a la propiedad de este objeto, para bien o para mal". Si ocurre un error mientras lo está construyendo, ¿por qué debería eso hacer una diferencia? El código de llamada todavía se deshará de él incluso si lanza una excepción.

Por ejemplo:

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 
    } 
} 

Editar: Por desgracia, si se produce una excepción en el interior CreateCommand, la variable cmd se nunca se ponía y el objeto no se elimina correctamente.

+3

No creo que eso sea cierto. Como la variable cmd nunca se asignará, el bloque que utiliza no llamará a cmd.Dispose(). –

+0

El problema es el lanzamiento en CommandText significará que newCommand nunca se devuelve a la instrucción using, por lo que no se eliminará allí. – Kleinux

+0

Buen punto, chicos. En ese caso, voto por algo similar al método genérico de winSharp93. –

Cuestiones relacionadas