usuario - que tipos de excepciones existen en c#
"Usar" construcción y manejo de excepciones (5)
En mi opinión, estás haciendo un uso indebido de la interfaz IDisposable
. Una práctica común es usar la interfaz para liberar recursos no administrados. Normalmente, el recolector de basura limpiará los objetos, pero en algunos casos, cuando ya no necesite el objeto, es posible que deba limpiarlo manualmente.
Sin embargo, en su caso, no está limpiando un objeto que ya no necesita; lo estás usando para forzar algo de lógica. Deberías usar otro diseño para eso.
La construcción " usar " parece increíblemente útil para situaciones que requieren partes finales tanto iniciales como separadas.
Ejemplo rápido para ilustrar:
using (new Tag("body")) {
Trace.WriteLine("hello!");
}
// ...
class Tag : IDisposable {
String name;
public Tag(String name) {
this.name = name;
Trace.WriteLine("<" + this.name + ">");
Trace.Indent();
}
public void Dispose() {
Trace.Unindent();
Trace.WriteLine("</" + this.name + ">")
}
}
La parte inicial se define como el constructor, la parte final es el método Dispose.
Sin embargo, a pesar de ser atractivo, esta construcción tiene una advertencia seria que proviene del hecho de que el método Dispose se llama desde dentro de un bloque finally. Entonces hay 2 problemas:
Debes evitar lanzar excepciones desde el bloque finally porque anularán la excepción original que se suponía atrapada.
No hay forma de saber dentro del método Dispose si se lanzó una excepción antes entre "principio" y "final" y, por lo tanto, no hay manera de manejar la parte "final" en consecuencia.
Estas 2 cosas hacen que el uso de esta construcción sea poco práctico, lo cual es un hecho muy triste. Ahora, mis preguntas son:
¿Mi comprensión de los problemas es correcta? ¿Es así como funciona realmente "usar"?
Si es así, ¿hay alguna manera de superar estos problemas y hacer un uso práctico de la construcción "que usa" distinta de la que fue originalmente diseñada (liberar recursos y limpiar)?
En caso de que no haya una forma práctica de "usar" para ser usado de esta manera. ¿Cuáles son los enfoques alternativos (para aplicar el contexto sobre algún código con las partes inicial y final)?
Su regla n. ° 1 se aplica con o sin using
, por lo que la regla n. ° 2 es el decisor real: opte por un try
/ catch
si debe distinguir entre situaciones cuando se ha lanzado una excepción y la finalización normal del programa.
Por ejemplo, si su capa de persistencia puede descubrir problemas en el proceso de utilizar una conexión de base de datos, su conexión debe cerrarse independientemente de que haya una excepción. En este caso, la construcción que using
es una elección perfecta.
En algunos casos, puede configurarlo using
específicamente para detectar la finalización normal frente a una finalización excepcional. Las transacciones ambientales proporcionan un ejemplo perfecto:
using(TransactionScope scope = new TransactionScope()) {
// Do something that may throw an exception
scope.Complete();
}
Si se llama al Dispose
scope
antes de llamar a Complete
, TransactionScope
sabe que se ha lanzado una excepción y anula la transacción.
Tiene razón al observar un problema con el diseño de bloques try / finally, que a su vez es un problema con el using
: no hay una forma limpia de código en un bloque finally
para saber si la ejecución del código continuará con la instrucción siguiente al bloque finally
, o si hay una excepción pendiente que se hará efectiva tan pronto como finally
ejecute el bloque finally
.
Realmente me gustaría ver una función de idioma en vb.net y C # que permita que un bloque finally
incluya un parámetro de Exception
(por ejemplo,
try { } finally (Exception ex) { ... }
donde la excepción pasada sería null
si el bloque try
saliera normalmente, o mantendría una excepción si no lo hiciera. Junto con esto, me gustaría ver una interfaz IDisposableEx
, que heredaría Dispose
e incluiría un método Dispose(Exception ex)
con la expectativa de que el código de usuario pasara en el ex
del bloque finally
. Cualquier excepción que ocurriera durante el Dispose
podría entonces envolver la excepción transferida (ya que tanto la excepción transferida como el hecho de que una excepción ocurrió en Dispose
serían relevantes).
De lo contrario, podría ser útil que .net proporcione un método que indique si hubo una excepción pendiente en el contexto actual. Desafortunadamente, no está claro cuál debería ser la semántica exacta de dicho método en varios casos de esquina. Por el contrario, la semántica de finally (Exception ex)
sería perfectamente clara. Tenga en cuenta, por cierto, que una implementación adecuada de finally (Exception ex)
requeriría que el lenguaje haga uso de filtros de excepciones, pero no requeriría exponer la capacidad de crear filtros arbitrarios.
La intención de la instrucción using
y de la interfaz IDisposable
es que el usuario elimine los recursos no administrados. Estos recursos suelen ser costosos y valiosos, por lo que deben eliminarse sin importar qué (por eso está finally
). El código en los bloques finally
no se puede anular, y puede bloquear el apagado de todo el dominio de la aplicación.
Ahora, es muy tentador abusar del using
para los fines que describes, y lo he hecho en el pasado. La mayoría de las veces no hay peligro allí . Pero si ocurre una excepción inesperada, se compromete todo el estado del procesamiento, no necesariamente querrá ejecutar la operación final; Entonces, en general, no hagas esto.
Una alternativa es usar una lambda, algo como esto:
public interface IScopable {
void EndScope();
}
public class Tag : IScopable {
private string name;
public Tag(string name) {
this.name = name;
Trace.WriteLine("<" + this.name + ">");
Trace.Indent();
}
public void EndScope() {
Trace.Unindent();
Trace.WriteLine("</" + this.name + ">");
}
}
public static class Scoping {
public static void Scope<T>(this T scopable, Action<T> action)
where T : IScopable {
action(scopable);
scopable.EndScope();
}
}
Úselo así:
new Tag("body").Scope(_ =>
Trace.WriteLine("hello!")
);
También puede crear otras implementaciones que ejecuten determinadas acciones en función de si se generaron excepciones de no.
En Nemerle, el lenguaje se puede extender con nueva sintaxis para respaldar esto.
No sé si esta fue la intención original de IDisposable
, pero Microsoft lo está usando de la manera que usted describe (separando las partes de inicio y fin). Un buen ejemplo de ello es la clase MVCForm, proporcionada por la infraestructura de mvc. Implementa IDisposable
y escribe la etiqueta final para el formulario, mientras que no puedo ver su implementación liberando recursos de hormiga (el escritor utilizado allí para mostrar el html parece mantenerse vivo incluso después de que se elimine el formulario).
Se ha escrito mucho sobre el using
bloques y cómo se "traga" las excepciones (un cliente wcf es una buena muestra, también puede encontrar tales discusiones aquí, en SO). Personalmente, también siento muchas veces que, aunque es conveniente usar el bloque de using
, no es 100% claro cuando debería y no debería usarse.
Por supuesto, usted PUEDE decir dentro del método de eliminación si lo alcanzó con nuestro sin un error, al agregar una bandera adicional a su clase y subirla dentro del bloque de using
, pero esto funcionará solo si la persona que va a utilizar su clase será consciente de esa bandera