Pregunta Determine si se está ejecutando en el bloque finally debido a la excepción lanzada


¿Es posible determinar si el código se está ejecutando actualmente en el contexto de un finally controlador como resultado de una excepción lanzada? Soy bastante aficionado a usar el IDisposable patrón para implementar la funcionalidad de determinación del alcance de entrada / salida, pero una preocupación con este patrón es que es posible que no desee necesariamente que se produzca el comportamiento de fin de ámbito si se produce una excepción en el cuerpo del using. Estaría buscando algo como esto:

public static class MyClass
{
    public static void MyMethod()
    {
        using (var scope = MyScopedBehavior.Begin())
        {
            //Do stuff with scope here
        }
    }
}

public sealed class MyScopedBehavior : IDisposable
{
    private MyScopedBehavior()
    {
        //Start of scope behavior
    }

    public void Dispose()
    {
        //I only want to execute the following if we're not unwinding
        //through finally due to an exception:
        //...End of scope behavior    
    }

    public static MyScopedBehavior Begin()
    {
        return new MyScopedBehavior();
    }
}

Hay otras maneras en que puedo lograr esto (pasar un delegado a una función que rodea la llamada con un comportamiento particular), pero tengo curiosidad si es posible hacerlo usando el IDisposable patrón.


En realidad, esto aparentemente se ha preguntado y respondido antes aquí. Es posible detectar de una manera muy hackosa. Realmente no usaría esa técnica, pero es interesante saber que es posible.


32
2017-07-21 16:21


origen


Respuestas:


Los medios para lograr esto que he visto requieren un método adicional:

public static void MyMethod()
{
    using (var scope = MyScopedBehavior.Begin())
    {
        //Do stuff with scope here
        scope.Complete(); // Tells the scope that it's good
    }
}

Al hacer esto, su objeto de alcance puede rastrear si está desechando debido a un error o una operación exitosa. Este es el enfoque adoptado por TransactionScope, por ejemplo (ver TransactionScope.Complete)


16
2017-07-21 16:27



Como punto adicional, IL le permite especificar SEH fault bloques que son similares a finally pero están ingresados solamente cuando se lanza una excepción, puede ver un ejemplo aquí, aproximadamente 2 / 3rds en la página. Desafortunadamente, C # no expone esta funcionalidad.


13
2017-07-21 16:44



Estaba buscando algo similar para las pruebas unitarias: tengo una clase de ayuda que utilizo para limpiar objetos después de una prueba y quiero mantener la sintaxis agradable y limpia de 'usar'. También quería la opción de no realizar una limpieza si la prueba falla. Lo que se me ocurrió es llamar Marshal.GetExceptionCode (). No sé si esto es apropiado para todos los casos, pero para el código de prueba parece funcionar bien.


7
2017-10-07 17:19



Lo mejor que se me ocurre es:

using (var scope = MyScopedBehavior.Begin())
{
  try
  {
    //Do stuff with scope here
  }
  catch(Exception)
  {
    scope.Cancel();
    throw;
  }
}

Por supuesto, scope.Cancel() se aseguraría de que no ocurra nada en Dispose ()


5
2017-07-21 16:27



El siguiente patrón evita el problema con el uso indebido de API, es decir, un método de finalización del alcance que no se llama, es decir, se omite por completo o no se llama debido a una condición lógica. Creo que esto responde a su pregunta más de cerca y es aún menos código para el usuario API.

Editar

Aún más sencillo después del comentario de Dan:

public class Bling
{
    public static void DoBling()
    {
        MyScopedBehavior.Begin(() =>
        {
            //Do something.
        }) ;
    }   
}

public static class MyScopedBehavior
{
    public static void Begin(Action action)
    {
        try
        {
            action();

            //Do additonal scoped stuff as there is no exception.
        }
        catch (Exception ex)
        {
            //Clean up...
            throw;
        }
    }
}   

3
2017-07-21 16:49



Creo que la mejor manera es usar escribir try/catch/finally cláusula de forma manual. Estudie un ítem del primer libro 'Effective c #'. Un buen hacker de C # debe saber exactamente a qué se expande. Ha cambiado un poco desde .Net 1.1 - ahora puede tener varios usando uno debajo de otro. Por lo tanto, use el reflector y estudia el código no azucarado

Luego, cuando escriba su propio código, use el using o escribe tus propias cosas No es terriblemente difícil, y es bueno saberlo.

Puede hacerse elegante con otros trucos, pero se siente demasiado pesado, e incluso no es eficiente. Permítanme incluir una muestra de código.

LAZY WAY:

using (SqlConnection cn = new SqlConnection(connectionString))
using (SqlCommand cm = new SqlCommand(commandString, cn))
{
    cn.Open();
    cm.ExecuteNonQuery();
}

MANO MANERA:

bool sawMyEx = false;
SqlConnection cn =  null;
SqlCommand cm = null;

try
{
    cn = new SqlConnection(connectionString);
    cm = new SqlCommand(commandString, cn);
    cn.Open();
    cm.ExecuteNonQuery();
}
catch (MyException myEx)
{
    sawMyEx = true; // I better not tell my wife.
    // Do some stuff here maybe?
}
finally
{
    if (sawMyEx)
    {
        // Piss my pants.
    }

    if (null != cm);
    {
        cm.Dispose();
    }
    if (null != cn)
    {
        cn.Dispose();
    }
}

1
2017-07-21 16:26



Sería (en mi humilde opinión) muy útil si hubiera una variante de IDisposable cuyo Dispose método aceptó un parámetro para indicar qué excepción, si alguna, estaba pendiente cuando se ejecutó. Entre otras cosas, en caso de que Dispose no puede realizar la limpieza esperada, podría lanzar una excepción que incluye información sobre la excepción anterior. También permitiría un Dispose método para lanzar una excepción si el código "olvida" hacer algo que se suponía que debía hacer dentro de un using bloquear, pero no sobrescribir ninguna otra excepción que pueda causar que el bloque que usa se cierre prematuramente. Desafortunadamente, no existe tal característica hasta el momento.

Existen numerosos artículos que sugieren medios para usar funciones de API para averiguar si existe una excepción pendiente. Un problema importante con estos enfoques es que es posible que el código se esté ejecutando en una finally bloque para un try que se completó con éxito, pero que puede estar anidado en un finally bloque cuyo try salió prematuramente. Incluso si Dispose método podría identificar que existía tal situación, no tendría forma de saber qué try bloquearlo "perteneció" a. Uno podría formular ejemplos donde se aplica cualquier situación.

Tal como están las cosas, el mejor enfoque es probablemente tener un método explícito de "éxito" y asumir el fracaso si no se llama, y ​​calcular que las consecuencias de olvidarse de llamar al método de "éxito" deberían ser obvias incluso si no se lanza ninguna excepción. Una cosa que puede ser útil como un simple método de utilidad sería algo así como

T Success<T>(T returnValue)
{
  Success();
  return T;
}

permitiendo código como:

return scopeGuard.Success(thingThatMightThrow());

más bien que

var result = thingThatMightThrow();
scopeGuard.Success();
return result;

1
2018-06-05 15:59



¿Por qué no simplemente disponer desde dentro de un try { } bloquear al final, y no utilizar un finalmente en absoluto? Este parece ser el comportamiento que estás buscando.

Esto también parece más realista en términos de cómo otros podrían usar su clase. ¿Estás seguro de que todos los que lo usan nunca querrán desechar en el caso de una excepción? ¿O debería este comportamiento ser manejado por el consumidor de la clase?


0
2017-07-21 16:30