2010-02-17 10 views
8

entiendo cómo funciona la try-finallytry-catch funciona y cómo, pero me encuentro con ellos (por lo general) en dos escenarios completamente diferentes:Caso de uso de try-catch-finally tanto con la captura y finalmente

  • try-finalmente (o using en C# y VB) se utiliza sobre todo en torno a algunos bloque de código de tamaño medio que utiliza algunos de los recursos que necesita ser dispuesto correctamente.
  • try-catch se utiliza sobre todo bien
    • en torno a un solo estado que puede fallar de una manera muy específica o
    • (como un cajón de sastre) a un muy alto nivel de la aplicación, por lo general directamente debajo de alguna acción de interfaz de usuario.

En mi experiencia, los casos en que un try-catch-finally sería apropiado, es decir, donde el bloque en el que yo quiero coger alguna excepción particular es exactamente el mismo bloque en el que Uso algún recurso desechable, son extremadamente raros. Sin embargo, los diseñadores de lenguaje de C#, VB y Java parecen considerar esto como un escenario muy común; los diseñadores de VB even think about adding catch to using.

¿Echo de menos algo? ¿O soy demasiado pedante con mi uso restrictivo de try-catch?


EDIT: Para aclarar: Mi código por lo general se parece a esto (funciones desenrolladas para mayor claridad):

Try 
    do something 
    Aquire Resource (e.g. get DB connection) 
    Try 
     do something 
     Try 
      do something that can fail 
     Catch SomeException 
      handle expected error 
     do something else... 
    Finally 
     Close Resource (e.g. close DB connection) 
    do something 
Catch all 
    handle unexpected errors 

la que sólo parece tener mucho más sentido de poner cualquiera de los dos enganches de la misma nivel como finalmente solo para evitar la sangría.

+0

¿De dónde sacó la sensación de que los diseñadores de idiomas lo consideran un escenario muy común? Es solo documentación, así que intentan escribir de una manera general; siempre usando try-catch-finally en los ejemplos –

+1

No todos los idiomas lo hacen de esta manera. F # solo permite que cualquiera de los equivalentes pruebe o atrape finalmente, pero no try-catch-finally. – kvb

+0

¿Su bloque "Catch All" libera el recurso? En caso afirmativo, lo tiene en dos lugares (vea el primero "finalmente"); de lo contrario, corre el riesgo de tener pérdidas de recursos. –

Respuesta

16

Una cita de MSDN

Un uso común de la captura y finalmente juntos es obtener y utilizar recursos en un bloque try, frente a circunstancias excepcionales en un bloque catch , y liberar los recursos en el bloque final.

Para que quede aún más claro, piense en el código que desea ejecutar, en el 99% de los casos funciona perfectamente bien, pero en algún lugar puede ocurrir un error, usted no sabe dónde y los recursos creados son costosos.

Para estar 100% seguro de que los recursos se eliminan, utiliza el bloque finally, sin embargo, desea señalar el 1% de los casos en los que se produce el error, por lo tanto, es posible que desee configurar el registro en la sección catch-ing.

Es un escenario muy común.

Edición - Un ejemplo práctico

Hay algunos buenos ejemplos aquí: SQL Transactions with SqlTransaction Class. Esta es solo una de las muchas maneras de usar Try, Catch & Finalmente, y lo demuestra muy bien, aunque un using(var x = new SqlTranscation) puede ser eficiente algunas veces.

Así que aquí va.

var connection = new SqlConnection(); 

var command = new SqlCommand(); 

var transaction = connection.BeginTransaction(); 

command.Connection = connection; 
command.Transaction = transaction; 

Aquí viene las partes más interesantes

/// 
/// Try to drop a database 
/// 
try 
{ 
    connection.Open(); 

    command.CommandText = "drop database Nothwind"; 

    command.ExecuteNonQuery(); 
} 

Así que imaginemos que lo anterior falla por alguna razón y se produce una excepción

/// 
/// Due to insufficient priviligies we couldn't do it, an exception was thrown 
/// 
catch(Exception ex) 
{ 
    transaction.Rollback(); 
} 

La transacción se deshace! Recuerde, los cambios que realice en los objetos dentro del try/catch no se revertirán, ¡ni siquiera si lo anida dentro de un uso!

/// 
/// Clean up the resources 
/// 
finally 
{ 

    connection.Close(); 
    transaction = null; 
    command = null; 
    connection = null; 
} 

¡Ahora se limpian los recursos!

+0

Gracias, pero luego me pregunto: ¿por qué querría solo registrar las excepciones que ocurren entre la obtención y la liberación del recurso? Ese es exactamente mi punto: el bloque de código en el que quiero hacer el registro (o cualquier otro tipo de manejo de excepciones) generalmente no es exactamente el mismo que el bloque de código donde uso un recurso específico. – Heinzi

+0

Es posible que desee realizar una reversión de transacción en el bloque catch y liberar los recursos en el bloque finally. –

+0

Transacción: Gracias, ese es exactamente el tipo de ejemplo que estaba buscando, uno donde la duración del recurso coincide exactamente con el alcance deseado del manejo de errores. +1, y sería bueno si pudieras agregar ese ejemplo a tu respuesta. – Heinzi

1

Casi siempre usaría try-catch-finaly en los casos en que necesita deshacerse de algo en todos los casos y utiliza el caso para registrar el error y/o informar al usuario.

6

I menudo escribir código que se parece a esto:

Handle h = ... 
try { 
    // lots of statements that use handle 
} catch (SomeException ex) { 
    // report exception, wrap it in another one, etc 
} catch (SomeOtherException ex) { 
    // ... 
} finally { 
    h.close(); 
} 

Así que tal vez lo que está siendo excesivamente pedante ... por ejemplo, poniendo un try/catch alrededor de declaraciones individuales. (A veces es necesario, pero en mi experiencia que normalmente no necesita ser tan fino.)

10

Ejemplo:

Try 
    Get DB connection from pool 
    Insert value in table 
    do something else... 
Catch 
    I have an error... e.g.: table already contained the row I was adding 
    or maybe I couldn't get the Connection at all??? 
Finally 
    if DB connection is not null, close it. 

Realmente no se puede obtener un ejemplo más "común". Lástima que algunas personas todavía se olviden de poner la conexión cercana en el último, donde pertenece, en lugar de en el bloque Try ... :(

+1

Es exactamente este ejemplo que no entiendo: ¿Por qué colocaría el bloque Catch para el Insert en este lugar en lugar de en torno a la instrucción Insert? ¿No representa eso el riesgo de atrapar accidentalmente una excepción que no quería atrapar? – Heinzi

+0

El punto que estaba tratando de ilustrar es que FINALMENTE * siempre * se ejecuta, con o sin excepción. Por lo tanto, mueva la versión del recurso allí. Esto hace que las cosas sean más autónomas. Y su forma de hacer las cosas significa tener una trampa para abrir la conexión, una para la escritura ... y posiblemente más para la parte "hacer otra cosa" que aún podría plantear varias excepciones. Y deberá recordar cerrar la conexión en todas estas partes. Arriesgarse por olvidarlo y tener filtraciones de recursos que son bastante difíciles de precisar. –

+1

Lo siento, pero esto simplemente no tiene sentido. Sé que * finalmente * siempre se ejecuta, por lo que pongo el lanzamiento de recursos allí. ¿Puedes dar un ejemplo concreto de cómo "mi manera" de hacer las cosas puede llevar a que no se libere un recurso? – Heinzi

-1

En mi experiencia, los casos en los que un try-catch-finally sería apropiado, es decir, donde el bloque en el que quiero capturar alguna excepción particular es exactamente el mismo bloque en el que uso algún recurso desechable, es extremadamente raro. Sin embargo, los diseñadores de lenguaje de C#, VB y Java parecen considerar esto como un escenario muy común; los diseñadores VB incluso pensar en añadir a la captura utilizando

que:

. me

:

try { 
    //use resource 
} catch (FirstException e) { 
    // log first exception 
} catch (SecondException e) { 
    // log first exception 
} catch (ThirdException e) { 
    // log first exception 
} finally { 
    // dispose resource 
} 

sentir defference)

+1

Y la diferencia más importante es que la primera versión del código está * rota * si '// use resource' arroja alguna otra excepción (por ejemplo, sin marcar). –

+0

gracias Stephen, me lo perdí.) – dart

+1

No creo que sea exacto - parece que Heinzi tendría un try-catch de alto nivel en la aplicación para registrar excepciones y un intento, finalmente, donde tienes tu try-catch -catch-catch-finally. El enfoque de Heinzi me parece apropiado. – kvb

3

No hay nada malo con anidación try/catch/finally bloques?. De hecho, uso esto con bastante frecuencia. Es decir. cuando utilizo algún recurso que debe eliminarse o cerrarse, pero solo quiero un bloque de captura alrededor de una unidad de código más grande que debe abortarse si se produce algún error en su interior.

try { 
    // some code 
    SomeResource res = new SomeResource(); 
    try { 
     res.use(); 
    } finally { 
     res.close(); 
    } 
    // some more code 
} catch(Exception ex) { 
    handleError(ex); 
} 

Esto cierra el recurso tan pronto como sea posible en cualquiera de los casos (error o no), pero aún se ocupa de todos los errores posibles de crear o utilizar el recurso en un solo bloque catch.

3

Creo que tiene toda la razón. Desde el . Net Framework Design Guidelines, escrito por algunos de los mejores arquitectos de Microsoft:

NO overcatch. Las excepciones deberían permitir que se propaguen por la pila de llamadas .

En un código bien escrito, try-finally [o usando] es mucho más común que try-catch. Puede parecer contrario a la intuición al principio, pero los bloques de captura no son necesarios en un sorprendente número de casos . Por otro lado, siempre debe considerar si try-finally [o using] podría ser de utilidad para la limpieza.

página 230 sección 7.2

12

No es una respuesta a su pregunta, sino un hecho divertido.

La implementación de Microsoft del compilador C# realmente no puede manejar try-catch-finally. Cuando analizar el código

try { Foo() } 
catch(Exception e) { Bar(e); } 
finally { Blah(); } 

que en realidad pretenden que el código fue escrito

try 
{ 
    try { Foo(); } 
    catch(Exception e) { Bar(e); } 
} 
finally { Blah(); } 

De esa manera el resto del compilador - el analizador semántico, el comprobador de accesibilidad, el generador de código, y así en - solo tiene que lidiar con try-catch y try-finally, nunca try-catch-finally. Un poco de una transformación temprana tonta en mi opinión, pero funciona.

+0

ambas piezas son iguales, ¿verdad? la única diferencia es que podrías tener aún más código justo antes de la 2da 'finalmente' – Atmocreations

+2

Bueno, si * no eran * lógicamente iguales, entonces no sería válido para nosotros hacer la transformación, por lo que no haríamos eso.Ya que * son * lógicamente iguales, podemos salirse con la suya con este engañoso truco. –

0

Otro uso sería desechar un identificador de archivo a un archivo adjunto de correo electrónico al usar los objetos de correo System.Web.Mail para enviar correos electrónicos. Descubrí esto cuando tuve que abrir programáticamente un Crystal Report, guardarlo en un disco, adjuntarlo a un correo electrónico y luego eliminarlo del disco. Se requería .Dispose() explícito en el documento Finally para asegurarme de que podía eliminarlo, especialmente en el caso de una excepción lanzada.

1

¿Qué tal algo como:

 
    Dim mainException as Exception = Nothing 

    Try 
    ... Start a transaction 
    ... confirm the transaction 
    Catch Ex as Exception 
    mainException = Ex 
    Throw 
    Finally 
    Try 
     ... Cleanup, rolling back the transaction if not confirmed 
    Catch Ex as Exception 
     Throw New RollbackFailureException(mainException, Ex) 
    End Try 
    End Try 

Suponiendo aquí que el RollbackFailureException incluye un campo "OriginalException", así como "InnerException", y acepta parámetros para ambos. Uno no quiere ocultar el hecho de que se produjo una excepción durante la reversión, pero tampoco quiere perder la excepción original que causó la reversión (y podría dar alguna pista sobre por qué ocurrió la reversión).

Cuestiones relacionadas