2010-05-26 27 views
225

Si un bloque finally arroja una excepción, ¿qué ocurre exactamente?¿Qué sucede si un bloque finally arroja una excepción?

Específicamente, qué sucede si la excepción se produce a mitad de un bloque finally. ¿Se invoca el resto de las declaraciones (después) en este bloque?

Soy consciente de que las excepciones se propagarán hacia arriba.

+8

¿Por qué no intentarlo? Pero en este tipo de cosas, la que más me gusta es regresar antes del final y luego devolver algo más desde el bloque final. :) – ANeves

+8

Deben ejecutarse todas las instrucciones en un bloque finally. No puede tener un retorno. http://msdn.microsoft.com/en-us/library/0hbbzekw(VS.80).aspx –

Respuesta

348

Si un bloque finally se produce una excepción lo exactamente sucede?

Esa excepción se propaga hacia fuera y hacia arriba, y será (puede) manejarse en un nivel superior.

Su bloque final será no se completará más allá del punto donde se produce la excepción.

Si el bloque finally se estaba ejecutando durante el manejo de una excepción anterior, entonces esa primera excepción se pierde.

C# 4 Especificación de idioma § 8.9.5: Si el bloque finally arroja otra excepción, finaliza el procesamiento de la excepción actual.

+33

+1: la única respuesta que ** completamente ** responde a la pregunta –

+3

Si se arroja una excepción desde el bloque 'try', se comerá. –

+8

A menos que sea una 'ThreadAbortException', entonces todo el bloque finally se terminará primero, ya que es una sección crítica. –

5

La excepción se propaga.

+1

@ bitbonk: de adentro hacia afuera, como de costumbre. – Piskvor

0

Lanza una excepción;) Puede detectar esa excepción en alguna otra cláusula catch.

1
public void MyMethod() 
{ 
    try 
    { 
    } 
    catch{} 
    finally 
    { 
     CodeA 
    } 
    CodeB 
} 

La forma en que las excepciones lanzadas por CodeA y CodeB se manejan es la misma.

Una excepción lanzada en un bloque finally tiene nada de especial, lo tratan como un saque de banda excepción por código B.

+0

¿Podrías dar más detalles? ¿Qué quieres decir con las excepciones son las mismas? –

+1

lo siento, mira mi EDIT –

90

Para preguntas como éstas por lo general abro un proyecto de aplicación de consola vacía en Visual Studio y escribo una pequeña muestra programa:

using System; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     try 
     { 
      try 
      { 
       throw new Exception("exception thrown from try block"); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("Inner catch block handling {0}.", ex.Message); 
       throw; 
      } 
      finally 
      { 
       Console.WriteLine("Inner finally block"); 
       throw new Exception("exception thrown from finally block"); 
       Console.WriteLine("This line is never reached"); 
      } 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine("Outer catch block handling {0}.", ex.Message); 
     } 
     finally 
     { 
      Console.WriteLine("Outer finally block"); 
     } 
    } 
} 

Cuando se ejecuta el programa, verá el orden exacto en el que se ejecutan catch y finally bloques. Tenga en cuenta que el código del bloque finally después se produce la excepción no se ejecutará (de hecho, en este programa de ejemplo de Visual Studio, incluso le advertirá de que ha detectado código inalcanzable):

 
Inner catch block handling exception thrown from try block. 
Inner finally block 
Outer catch block handling exception thrown from finally block. 
Outer finally block 

observación adicional

Como señaló Michael Damatov, una excepción del bloque try se "comerá" si no se maneja en un bloque (interno) catch. De hecho, en el ejemplo anterior, la excepción relanzada no aparece en el bloque catch externo. Para hacer que el aspecto aún más claro en el siguiente ejemplo ligeramente modificado:

using System; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     try 
     { 
      try 
      { 
       throw new Exception("exception thrown from try block"); 
      } 
      finally 
      { 
       Console.WriteLine("Inner finally block"); 
       throw new Exception("exception thrown from finally block"); 
       Console.WriteLine("This line is never reached"); 
      } 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine("Outer catch block handling {0}.", ex.Message); 
     } 
     finally 
     { 
      Console.WriteLine("Outer finally block"); 
     } 
    } 
} 

Como se puede ver en la salida la excepción interna está "perdido" (es decir,ignorado):

 
Inner finally block 
Outer catch block handling exception thrown from finally block. 
Outer finally block 
+2

Porque LANZAS la Excepción en tu captura interna, 'Inner finally block' nunca será alcanzado en este ejemplo –

+3

@Theofanis Pantelides: No, un bloque 'finally' (casi) siempre se ejecutará, esto es válido también en este caso para el bloque interno final (simplemente pruebe el programa de muestra usted mismo (Un bloque finally no se ejecutará en el caso de una excepción no recuperable, por ejemplo, una 'EngineExecutionException', pero en tal caso su programa terminará inmediatamente de todos modos) –

+8

+1: Una respuesta más útil que simplemente citar la especificación. –

9

Si hay una excepción a la espera (cuando el bloque try tiene una finally pero sin catch), la nueva excepción de que uno reemplaza.

Si no hay una excepción pendiente, funciona igual que lanzar una excepción fuera del bloque finally.

+0

Una excepción también podría estar pendiente si there * es * un bloque 'catch' coincidente que (re) arroja una excepción. – stakx

1

Lanzar una excepción mientras otra excepción está activa dará lugar a la primera excepción reemplazada por la segunda excepción (posterior).

Aquí hay un código que ilustra lo que sucede:

public static void Main(string[] args) 
    { 
     try 
     { 
      try 
      { 
       throw new Exception("first exception"); 
      } 
      finally 
      { 
       //try 
       { 
        throw new Exception("second exception"); 
       } 
       //catch (Exception) 
       { 
        //throw; 
       } 
      } 
     } 
     catch (Exception e) 
     { 
      Console.WriteLine(e); 
     } 
    } 
  • ejecutar el código y verá "segunda excepción"
  • Descomentar las declaraciones try y catch y verá "primera excepción"
  • También elimine el comentario del lanzamiento; declaración y verás "segunda excepción" de nuevo. Hace
+0

Vale la pena señalar que es posible limpiar una excepción "grave" que solo se detectaría fuera de un bloque de código en particular para arrojar una excepción que se atrape y maneje dentro de ella. Usando filtros de excepción (disponible en vb.net, aunque no en C#) es posible detectar esta condición. No hay mucho que el código pueda hacer para "manejarlo", aunque si uno está usando cualquier tipo de estructura de registro, es casi seguro que vale la pena iniciar sesión. El enfoque C++ de tener excepciones que ocurren dentro de la limpieza desencadenar un colapso del sistema es feo, pero tener excepciones desaparecen es en mi humillación. – supercat

1

algunos meses también se enfrentaron a algo como esto,

private void RaiseException(String errorMessage) 
    { 
     throw new Exception(errorMessage); 
    } 

    private void DoTaskForFinally() 
    { 
     RaiseException("Error for finally"); 
    } 

    private void DoTaskForCatch() 
    { 
     RaiseException("Error for catch"); 
    } 

    private void DoTaskForTry() 
    { 
     RaiseException("Error for try"); 
    } 


     try 
     { 
      /*lacks the exception*/ 
      DoTaskForTry(); 
     } 
     catch (Exception exception) 
     { 
      /*lacks the exception*/ 
      DoTaskForCatch(); 
     } 
     finally 
     { 
      /*the result exception*/ 
      DoTaskForFinally(); 
     } 

Para resolver tal problema que hice una clase de utilidad como

class ProcessHandler : Exception 
{ 
    private enum ProcessType 
    { 
     Try, 
     Catch, 
     Finally, 
    } 

    private Boolean _hasException; 
    private Boolean _hasTryException; 
    private Boolean _hasCatchException; 
    private Boolean _hasFinnallyException; 

    public Boolean HasException { get { return _hasException; } } 
    public Boolean HasTryException { get { return _hasTryException; } } 
    public Boolean HasCatchException { get { return _hasCatchException; } } 
    public Boolean HasFinnallyException { get { return _hasFinnallyException; } } 
    public Dictionary<String, Exception> Exceptions { get; private set; } 

    public readonly Action TryAction; 
    public readonly Action CatchAction; 
    public readonly Action FinallyAction; 

    public ProcessHandler(Action tryAction = null, Action catchAction = null, Action finallyAction = null) 
    { 

     TryAction = tryAction; 
     CatchAction = catchAction; 
     FinallyAction = finallyAction; 

     _hasException = false; 
     _hasTryException = false; 
     _hasCatchException = false; 
     _hasFinnallyException = false; 
     Exceptions = new Dictionary<string, Exception>(); 
    } 


    private void Invoke(Action action, ref Boolean isError, ProcessType processType) 
    { 
     try 
     { 
      action.Invoke(); 
     } 
     catch (Exception exception) 
     { 
      _hasException = true; 
      isError = true; 
      Exceptions.Add(processType.ToString(), exception); 
     } 
    } 

    private void InvokeTryAction() 
    { 
     if (TryAction == null) 
     { 
      return; 
     } 
     Invoke(TryAction, ref _hasTryException, ProcessType.Try); 
    } 

    private void InvokeCatchAction() 
    { 
     if (CatchAction == null) 
     { 
      return; 
     } 
     Invoke(TryAction, ref _hasCatchException, ProcessType.Catch); 
    } 

    private void InvokeFinallyAction() 
    { 
     if (FinallyAction == null) 
     { 
      return; 
     } 
     Invoke(TryAction, ref _hasFinnallyException, ProcessType.Finally); 
    } 

    public void InvokeActions() 
    { 
     InvokeTryAction(); 
     if (HasTryException) 
     { 
      InvokeCatchAction(); 
     } 
     InvokeFinallyAction(); 

     if (HasException) 
     { 
      throw this; 
     } 
    } 
} 

y usados ​​como esto

try 
{ 
    ProcessHandler handler = new ProcessHandler(DoTaskForTry, DoTaskForCatch, DoTaskForFinally); 
    handler.InvokeActions(); 
} 
catch (Exception exception) 
{ 
    var processError = exception as ProcessHandler; 
    /*this exception contains all exceptions*/ 
    throw new Exception("Error to Process Actions", exception); 
} 

, pero si desea utilizar parámetros y devolver tipos que es otra historia

2

Tuve que hacer esto para detectar un error al intentar cerrar una secuencia que nunca se abrió debido a una excepción.

errorMessage = string.Empty; 

try 
{ 
    byte[] requestBytes = System.Text.Encoding.ASCII.GetBytes(xmlFileContent); 

    webRequest = WebRequest.Create(url); 
    webRequest.Method = "POST"; 
    webRequest.ContentType = "text/xml;charset=utf-8"; 
    webRequest.ContentLength = requestBytes.Length; 

    //send the request 
    using (var sw = webRequest.GetRequestStream()) 
    { 
     sw.Write(requestBytes, 0, requestBytes.Length); 
    } 

    //get the response 
    webResponse = webRequest.GetResponse(); 
    using (var sr = new StreamReader(webResponse.GetResponseStream())) 
    { 
     returnVal = sr.ReadToEnd(); 
     sr.Close(); 
    } 
} 
catch (Exception ex) 
{ 
    errorMessage = ex.ToString(); 
} 
finally 
{ 
    try 
    { 
     if (webRequest.GetRequestStream() != null) 
      webRequest.GetRequestStream().Close(); 
     if (webResponse.GetResponseStream() != null) 
      webResponse.GetResponseStream().Close(); 
    } 
    catch (Exception exw) 
    { 
     errorMessage = exw.ToString(); 
    } 
} 

si se creó la WebRequest pero ocurrió un error de conexión durante el

using (var sw = webRequest.GetRequestStream()) 

entonces el fin sería capturar una excepción tratando de cerrar las conexiones que pensaban estaba abierta debido a que el WebRequest había sido creado.

Si el fin ni tienen un try-catch en el interior, este código podría causar una excepción no controlada durante la limpieza de la WebRequest

if (webRequest.GetRequestStream() != null) 

desde allí el código podría salir sin el manejo adecuado del error que ocurrió y por lo tanto causando problemas para el método de llamada.

Espero que esto ayude como ejemplo

2

rápida (y bastante obvio) fragmento de código para salvar "excepción original" (lanzado en try bloque) y el sacrificio "finalmente excepción" (lanzado en finally bloque), en caso de un original es más importante para usted:

try 
{ 
    throw new Exception("Original Exception"); 
} 
finally 
{ 
    try 
    { 
     throw new Exception("Finally Exception"); 
    } 
    catch 
    { } 
} 

Cuando se ejecuta el código de "excepción original" se propaga la pila de llamadas, y "Finalmente Excepción" se pierde.

1

La excepción se propaga y debe manejarse en un nivel superior. Si la excepción no se maneja en el nivel superior, la aplicación se bloquea. La ejecución del bloque "finalmente" se detiene en el punto donde se lanza la excepción.

Independientemente de si hay una excepción o no se garantiza la ejecución definitiva del bloque.

  1. Si el "fin" de bloque se ejecuta después de una excepción se ha producido en el bloque try,

  2. y si esa excepción no se maneja

  3. y si el bloque finally lanza una excepción

Luego se pierde la excepción original que ocurrió en el bloque try.

public class Exception 
{ 
    public static void Main() 
    { 
     try 
     { 
      SomeMethod(); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine(ex.Message); 
     } 
    } 

    public static void SomeMethod() 
    { 
     try 
     { 
      // This exception will be lost 
      throw new Exception("Exception in try block"); 
     } 
     finally 
     { 
      throw new Exception("Exception in finally block"); 
     } 
    } 
} 

Great article for Details

Cuestiones relacionadas