pattern net example c# .net idisposable

c# - net - override void dispose



Interceptar una excepción dentro de IDisposable.Disponer (10)

En el método IDisposable.Dispose , ¿hay alguna forma de averiguar si se está IDisposable.Dispose una excepción?

using (MyWrapper wrapper = new MyWrapper()) { throw new Exception("Bad error."); }

Si se lanza una excepción en la declaración de using , quiero saber al respecto cuando se IDisposable objeto IDisposable .


En lugar del azúcar sintáctico de la declaración de uso, ¿por qué no simplemente implementar su propia lógica para esto? Algo como:

try { MyWrapper wrapper = new MyWrapper(); } catch (Exception e) { wrapper.CaughtException = true; } finally { if (wrapper != null) { wrapper.Dispose(); } }


Esto capturará las excepciones lanzadas directamente o dentro del método de eliminación:

try { using (MyWrapper wrapper = new MyWrapper()) { throw new MyException("Bad error."); } } catch ( MyException myex ) { //deal with your exception } catch ( Exception ex ) { //any other exception thrown by either //MyWrapper..ctor() or MyWrapper.Dispose() }

Pero esto depende de que usen este código, parece que quieres que MyWrapper lo haga.

La instrucción using es solo para asegurarse de que se llama a Dispose siempre. Realmente está haciendo esto:

MyWrapper wrapper; try { wrapper = new MyWrapper(); } finally { if( wrapper != null ) wrapper.Dispose(); }

Parece que lo que quieres es:

MyWrapper wrapper; try { wrapper = new MyWrapper(); } finally { try{ if( wrapper != null ) wrapper.Dispose(); } catch { //only errors thrown by disposal } }

Sugeriría ocuparse de esto en su implementación de Dispose: de todos modos, debe manejar cualquier problema durante la eliminación.

Si está bloqueando algún recurso donde necesita que los usuarios de su API lo liberen de alguna manera, considere tener un método Close() . También debe llamarlo (si no lo ha hecho), pero los usuarios de su API también podrían llamarlo ellos mismos si necesitaran un control más preciso.


James, todo lo que el wrapper puede hacer es registrar sus propias excepciones. No puede obligar al consumidor de wrapper a registrar sus propias excepciones. Eso no es para lo que IDisposable es. IDisposable está destinado a la liberación semideterminista de recursos para un objeto. Escribir el código correcto IDisposable no es trivial.

De hecho, ni siquiera se requiere que el consumidor de la clase llame al método de eliminación de clases, ni se le exige usar un bloque de uso, por lo que todo se descompone.

Si lo mira desde el punto de vista de la clase contenedora, ¿por qué debería importarle que estuviera presente dentro de un bloque de uso y hubo una excepción? ¿Qué conocimiento trae eso? ¿Es un riesgo de seguridad tener un código de terceros informado de los detalles de las excepciones y del seguimiento de la pila? ¿Qué puede hacer la wrapper si hay una división por cero en un cálculo?

La única manera de registrar excepciones, independientemente de IDisposable, es try-catch y luego volver a lanzar en el catch.

try { // code that may cause exceptions. } catch( Exception ex ) { LogExceptionSomewhere(ex); throw; } finally { // CLR always tries to execute finally blocks }

Menciona que está creando una API externa. Tendría que ajustar cada llamada en el límite público de su API con try-catch para registrar que la excepción provino de su código.

Si está escribiendo una API pública, entonces realmente debería leer las Pautas de diseño de frameworks: convenciones, modismos y patrones para bibliotecas .NET reutilizables (Microsoft .NET Development Series) - 2da edición . 1st Edition .

Si bien no los defiendo, he visto IDisposable usado para otros patrones interesantes:

  1. Semántica de transacciones de retrotracción automática. La clase de transacción revertiría la transacción en Dispose si aún no se ha comprometido.
  2. Bloques de código temporizados para el registro. Durante la creación del objeto, se grabó una marca de tiempo, y en Dispose se calculó TimeSpan y se escribió un evento de registro.

* Estos patrones se pueden lograr con otra capa de direccionamiento indirecto y delegados anónimos fácilmente y sin tener que sobrecargar la semántica IDisposable. La nota importante es que su envoltorio IDisposable es inútil si usted o un miembro del equipo se olvida de usarlo correctamente.


No solo es posible descubrir si se lanzó una excepción cuando se desecha un objeto desechable, sino que incluso se puede obtener la excepción que se arrojó dentro de la cláusula finally con un poco de magia. La biblioteca My Tracing de la herramienta ApiChange emplea este método para rastrear excepciones dentro de una instrucción using. Más información sobre cómo funciona esto se puede encontrar aquí .

Atentamente, Alois Kraus


No , no hay forma de hacer esto en el marco .Net, no se puede descifrar la excepción actual que se lanza en una cláusula finally.

Ver esta publicación en mi blog , para una comparación con un patrón similar en Ruby, resalta las lagunas que creo que existen con el patrón IDisposable.

Ayende tiene un truco que te permitirá detectar una excepción , sin embargo, no te dirá qué excepción fue.


Si quieres mantenerte puramente dentro de .net, dos enfoques que sugeriría serían escribir un contenedor "try-catch-finally", que aceptaría delegados para las diferentes partes, o bien escribir un contenedor "usar estilo", que acepte un método para ser invocado, junto con uno o más objetos IDsposable que deben ser eliminados después de que se complete.

Una envoltura de "estilo de uso" podría manejar la eliminación en un bloque try-catch y, si se lanzan excepciones en la eliminación, envuélvalas en una CleanupFailureException que contendría los fallos de eliminación, así como cualquier excepción que haya ocurrido en el delegado principal o agregue algo a la propiedad de "Datos" de la excepción con la excepción original. Yo preferiría incluir cosas en una CleanupFailureException, ya que una excepción que ocurre durante la limpieza generalmente indicará un problema mucho más grande que uno que ocurre en el procesamiento de la línea principal; Además, se podría escribir una CleanupFailureException para incluir múltiples excepciones anidadas (si hay ''n'' objetos IDsposable, podría haber n + 1 excepciones anidadas: una de la línea principal y una de cada Dispose).

Un contenedor "try-catch-finally" escrito en vb.net, aunque invocable desde C #, podría incluir algunas características que de otro modo no estarían disponibles en C #, incluida la capacidad de expandirlo a "try-filter-catch-fault-finally" bloquear, donde el código de "filtro" se ejecutaría antes de desenrollar la pila de una excepción y determinar si la excepción debería capturarse, el bloque de "fallas" contendría un código que solo se ejecutaría si ocurriera una excepción, pero que en realidad no capturaría y los bloques "fault" y "finally" recibirán parámetros que indican qué excepción (si la hubo) durante la ejecución del "try", y si el "try" se completó correctamente (nota, por cierto, que lo haría ser posible que el parámetro de excepción no sea nulo incluso si la línea principal se completa; el código de C # puro no podría detectar dicha condición, pero el contenedor de vb.net podría).


Puede hacer esto mediante la implementación del método Dispose para la clase "MyWrapper". En el método de eliminación, puede verificar si hay una excepción de la siguiente manera

public void Dispose() { bool ExceptionOccurred = Marshal.GetExceptionPointers() != IntPtr.Zero || Marshal.GetExceptionCode() != 0; if(ExceptionOccurred) { System.Diagnostics.Debug.WriteLine("We had an exception"); } }


No es posible capturar la excepción en el método Dispose() .

Sin embargo, es posible verificar Marshal.GetExceptionCode() en Dispose para detectar si se produjo una excepción, pero no confiaría en eso.

Si no necesita una clase y desea simplemente capturar la Excepción, puede crear una función que acepte una lambda que se ejecute dentro de un bloque try / catch, algo como esto:

HandleException(() => { throw new Exception("Bad error."); }); public static void HandleException(Action code) { try { if (code != null) code.Invoke(); } catch { Console.WriteLine("Error handling"); throw; } }

Como ejemplo, puede usar un método que realice automáticamente un Commit () o Rollback () de una transacción y realice algún registro. De esta forma, no siempre necesitas un bloque try / catch.

public static int? GetFerrariId() { using (var connection = new SqlConnection("...")) { connection.Open(); using (var transaction = connection.BeginTransaction()) { return HandleTranaction(transaction, () => { using (var command = connection.CreateCommand()) { command.Transaction = transaction; command.CommandText = "SELECT CarID FROM Cars WHERE Brand = ''Ferrari''"; return (int?)command.ExecuteScalar(); } }); } } } public static T HandleTranaction<T>(IDbTransaction transaction, Func<T> code) { try { var result = code != null ? code.Invoke() : default(T); transaction.Commit(); return result; } catch { transaction.Rollback(); throw; } }


Puedes extender IDisposable con el método Complete y usar un patrón así:

using (MyWrapper wrapper = new MyWrapper()) { throw new Exception("Bad error."); wrapper.Complete(); }

Si se lanza una excepción dentro de la instrucción de using , Complete no se Dispose antes de Dispose .

Si desea saber qué excepción exacta se emite, suscríbase en el evento AppDomain.CurrentDomain.FirstChanceException y almacene la última excepción lanzada en la ThreadLocal<Exception> .

Tal patrón implementado en clase TransactionScope .


En mi caso, quería hacer esto para iniciar sesión cuando un microservicio falla. Ya tengo instalado un using para limpiar correctamente justo antes de que se cierre una instancia, pero si eso se debe a una excepción, quiero ver por qué, y no me gusta la respuesta.

En lugar de tratar de hacer que funcione en Dispose() , tal vez haga un delegado para el trabajo que necesita hacer, y luego ajuste su captura de excepciones allí. Entonces en mi registrador MyWrapper, agrego un método que toma una Acción / Func:

public void Start(Action<string, string, string> behavior) try{ var string1 = "my queue message"; var string2 = "some string message"; var string3 = "some other string yet;" behaviour(string1, string2, string3); } catch(Exception e){ Console.WriteLine(string.Format("Oops: {0}", e.Message)) } }

Para implementar:

using (var wrapper = new MyWrapper()) { wrapper.Start((string1, string2, string3) => { Console.WriteLine(string1); Console.WriteLine(string2); Console.WriteLine(string3); } }

Dependiendo de lo que necesite hacer, esto puede ser demasiado restrictivo, pero funcionó para lo que necesitaba.