2011-01-17 11 views
8

¿Hay alguna manera de obtener una referencia a una excepción dentro de un bloque finally en un iterator function or property that allow try..finally but not try..catch?Obteniendo referencia a una excepción en el bloque finally en un iterador

No voy a usarlo para cambiar o interferir con el flujo de control, pero me gustaría poder obtener una referencia a la excepción en el bloque finally de todos modos (si se arrojó), para leer de él y posiblemente agregue cosas al Data member.

Entiendo que debido a la naturaleza de las clases generadas por el compilador de los iteradores, probablemente no sea posible/permitido por el mismo motivo por el que try..catch alrededor de una declaración de rendimiento no está permitido en primer lugar. Pero todavía espero que haya alguna forma (o incluso un truco feo) de conseguir la excepción de todos modos.

ejemplo simplificado:

IEnumerable<SomeClass> Something 
get 
{ 
    try 
    { 
    throw new SomeException(); 
    yield return new SomeClass(); 
    } 
    finally 
    { 
    Exception ex = ... // <= TODO - get hold of the exception here [if one was thrown]... 
    } 
} 

Respuesta

17

Ésta es una muy interesante pregunta.

Recuerde que en Linq, se proporcionan muchos operadores estándar que encadenan eficazmente. Actualmente no hay uno que le permita ajustar el manejo de excepciones personalizado alrededor de una secuencia interna.

Así que mi sugerencia es escribir una nueva que le permite especificar una acción que se ocupa de cualquier excepción que se produce durante la ejecución de IEnumerator.MoveNext:

public static class EnumerableExceptions 
{ 
    public static IEnumerable<TItem> Catch<TItem, TEx>(
     this IEnumerable<TItem> source, 
     Action<TEx> handler) where TEx : Exception 
    { 
     using (var enumerator = source.GetEnumerator()) 
     { 
      for (; ;) 
      { 
       try 
       { 
        if (!enumerator.MoveNext()) 
         yield break; 
       } 
       catch (TEx x) 
       { 
        handler(x); 
        yield break; 
       } 

       yield return enumerator.Current; 
      } 
     } 
    } 
} 

Así que ahora suponiendo que teníamos esto:

public class NastyException : Exception { } 

public static IEnumerable<String> StringYielder() 
{ 
    yield return "apple"; 
    yield return "banana"; 

    throw new NastyException(); 

    yield return "oracle"; 
    yield return "grapefruit"; 
    yield return "microsoft"; 
} 

Nos gustaría poder envolver todo el cuerpo en un try/catch, lo cual es lamentablemente ilegal. Pero lo que podemos hacer es envolver la secuencia generada:

public static IEnumerable<String> LoggingStringYielder() 
{ 
    return StringYielder().Catch(
     (NastyException ex) => 
      Console.WriteLine("Exception caught: " + ex.StackTrace)); 
} 

Eso es, me sale una secuencia llamando a la "cruda" StringYielder método, y luego aplicar el nuevo operador Catch a la misma, especificando qué hacer si se produce un cierto tipo de excepción. Aquí solo voy a imprimir el seguimiento de la pila.

Así que si hago esto:

foreach (var str in LoggingStringYielder()) 
    Console.WriteLine(str); 

El programa se completa sin que se caiga, y la salida es:

apple 
banana 
Exception caught: at ConsoleApplication7.Program.<StringYielder>.. blah 

Así que, aunque no se puede poner un intento de captura en todo el código dentro de la método iterador original, ahora puede "envolverlo" alrededor de ese método iterador. Es como una forma no intrusiva de inyectar el manejo de excepciones alrededor del código entre cada yield return.

¡Actualización de bonificación!

a ser muy exigente con la forma en que redactado esta última frase:

  • En primer lugar se puede tirar antes de la primera yield return y es tratado de la misma manera, como el código se ejecuta en la primera llamada a MoveNext. Entonces, "... el código antes de cada ..." hubiera sido más preciso que "... el código entre cada uno ...".

  • En segundo lugar, un yield return puede aceptar una expresión que debe evaluarse y que puede arrojarse durante la evaluación. Eso debe considerarse como código que se ejecuta antes de que ocurra el yield return, aunque sintácticamente aparece después de él.

1

finally bloques son para siempre ejecutada limpieza y por lo tanto no están destinados a manipular con excepciones - no puede ser fácilmente sin excepción pendiente en absoluto.

Uso del bloque catch con un volver a lanzar debería funcionar para usted:

try 
{ 
    // .. something throws here .. 
} 
catch (Exception ex) 
{ 
    // .. do whatever you need with ex here .. 

    // and pass it on 
    throw; 
} 
+1

Ok, actualicé la pregunta para reflejar que quiero obtener la excepción _if_ se arrojó. Aún así, no se permite capturar alrededor del rendimiento ... – KristoferA

5

¿Qué tal se mueve todo el código que podría generar una excepción en un intento anidada/catch:

IEnumerable<int> GetFoo() 
{ 
    for (int i = -10; i < 10; i++) 
    { 
     Exception ex = null; 
     try 
     { 
      int result = 0; 
      try 
      { 
       result = 10/i; 
      } 
      catch (Exception e) // Don't normally do this! 
      { 
       ex = e; 
       throw; 
      } 
      yield return result; 
     } 
     finally 
     { 
      if (ex != null) 
      { 
       // Use ex here 
      } 
     } 
    } 
} 

Con el Por encima del patrón, sin embargo, puede hacer todo lo que necesita dentro del bloque catch, lo que sería más simple: puede deshacerse del bloque try/finally circundante.

+0

Ojalá pudiera, pero eso significaría una refactorización importante. El problema es que hay iteradores anidados involucrados, y estoy consultando uno o varios iteradores, recorriendo los resultados, y _yield return_ ing stuff. (en una tonelada de lugares). Se puede refactorizar usando el enfoque anterior, pero quiero ir a lo seguro con un mínimo de cambios de código para evitar introducir regresiones, etc. Por eso prefiero que de alguna manera pueda obtener una referencia a la excepción del CLR dentro del bloque final. . :) – KristoferA

2

Saber qué excepción está pendiente, si la hay, durante un finalizador es una buena habilidad. VB.net lo hace posible, aunque incómodo. C# no. En vb.net la técnica es:

 
    Dim PendingException as Exception = Nothing 
    Try 
    .. Do Whatever 
    PendingException = Nothing ' Important -- See text 
    Catch Ex As Exception When CopyFirstArgumentToSecondAndReturnFalse(Ex, PendingException) 
    Throw ' Should never happen if above function returns false like it should 
    Finally 
    ' PendingException will be Nothing if Try completed normally, or hold exception if not. 
    Try 
     .. Cleanup 
    Catch Ex as Exception 
     Throw New FailedCleanupException(Ex, PendingException) 
    End Try 
    End Try 

Tenga en cuenta que esto puede ser una técnica muy útil si el código que se desencadena por una excepción está garantizada para llegar a cualquiera de Regeneración de la excepción o lanzar una nueva excepción agregado. Entre otras cosas, si finalmente la excepción no se va a controlar, la captura del depurador "Excepción no controlada" se activará cuando se produzca la excepción original, en lugar de la última vez que se vuelve a lanzar. Esto puede facilitar considerablemente la depuración, ya que habrá una gran cantidad de estado del programa disponible para el depurador que, de lo contrario, se descartaría.

Tenga en cuenta también que PendingException se borra explícitamente al final del bloque Try principal. Es posible que PendingException tenga un valor incluso si no hay una excepción pendiente. Esto podría ocurrir si algo dentro de un bloque Try doblemente anidado arroja una excepción que no será capturada dentro de nuestro bloque try, pero la cláusula Finally del bloque Try interno arroja una excepción que queda atrapada dentro del bloque Try anidado individualmente. En ese caso, la excepción original efectivamente desaparece. Puede ser bueno generar una entrada de registro especial si el "CopyFirstParameterToSecondAndReturnFalse" o "PendingException = Nothing" se ejecuta cuando PendingException no es nulo, ya que ese escenario probablemente representaría un error que no se puede registrar en ningún otro lado, pero si hay múltiples bloques de captura anidados, podrían generar entradas de registro redundantes.

Dado que C# no es compatible con el filtrado de excepciones necesario para este enfoque, puede ser útil escribir un contenedor VB que pueda llamar delegados C# pero proporcione la lógica de manejo de excepciones necesaria.

EDITAR No es posible hacer un retorno de rendimiento dentro de un bloque try-catch, pero no me gustaría ver que causaría un problema con algo como:

 
{ 
    Exception ex = null; 

    try 
    { 
    CaptureExceptionInfoButDontCatch(ex,{ 
     /* Stuff that might throw */ 
    }); 
    yield return whatever; 
    CaptureExceptionInfoButDontCatch(ex,{ 
     /* More that might throw */ 
    }); 
    } 
    finally 
    { 
    /* If ex is nothing, exception occurred */ 
    } 
} 
+0

Gracias. Solo hay una trampa: incluso si C# podría hacerlo de la manera VB, el problema aquí es que los iteradores (específicamente la palabra clave _yield_) no permiten que se use 'catch'. Y debido a la naturaleza diferida de los iteradores, el ajuste tampoco funcionará; el contenedor simplemente devolvería una referencia a [una instancia de] la clase de iterador generada por el compilador y cada vez que se ejecute realmente el código, try/catch ya está fuera del alcance ... – KristoferA

+1

El retorno de rendimiento no permite "rentabilidad de rendimiento" dentro un bloque try-catch, pero eso no debería impedir envolver el código arriba y debajo del retorno de rendimiento en bloques que capturen la excepción sin capturarla. Ver editar arriba. – supercat

Cuestiones relacionadas