practice - ¿Cómo capturar la excepción original(interior) en C#?
throw c# ejemplo (5)
Buena pregunta y buenas respuestas!
Solo quiero complementar las respuestas ya dadas con algunos pensamientos adicionales:
Por un lado estoy de acuerdo con dasblinkenlight y los otros usuarios. Si detecta una excepción para volver a lanzar una excepción de un tipo diferente con la excepción original establecida como la excepción interna, debe hacer esto por el único motivo de mantener el contrato del método . (El acceso al servidor SQL es un detalle de la implementación que la persona que llama no es / no debe / no puede conocer, por lo que no puede anticipar que se SqlException
una SqlException
(o DbException
))
Sin embargo, la aplicación de esta técnica tiene algunas implicaciones de las que uno debe ser consciente:
- Estás ocultando la causa raíz del error. En su ejemplo, le está informando a la persona que llama que una regla de negocios no era válida (?), Violada (?), Etc., cuando en realidad hubo un problema al acceder a la base de datos (lo que quedaría claro de inmediato si se permitiera que la
DbException
seDbException
la pila de llamadas más lejos). - Está ocultando la ubicación donde se produjo el error originalmente. La propiedad
StackTrace
de la excepción capturada apuntará a un bloque de captura lejos de la ubicación en la que se produjo el error originalmente. Esto puede hacer que la depuración sea notoriamente difícil a menos que también tenga mucho cuidado de registrar los rastros de pila de todas las excepciones internas. (Esto es especialmente cierto una vez que el software ha sido implementado en producción y no tiene medios para adjuntar un depurador ...)
Dado que .NET no le permite modificar el Mensaje de una excepción para incluir información adicional, ¿cuál es el mecanismo previsto para detectar las excepciones originales?
Es cierto que .NET no le permite alterar el Mensaje de una excepción. Sin embargo, proporciona otro mecanismo para proporcionar información adicional a una excepción a través del diccionario Exception.Data
. Entonces, si todo lo que quiere hacer es agregar datos adicionales a una excepción, entonces no hay razón para envolver la excepción original y lanzar una nueva. En su lugar solo haz
public void DoStuff(String filename)
{
try {
// Some file I/O here...
}
catch (IOException ex) {
// Add filename to the IOException
ex.Data.Add("Filename", filename);
// Send the exception along its way
throw;
}
}
Estoy llamando a una función que lanza una excepción personalizada:
GetLockOwnerInfo(...)
Esta función, a su vez, está llamando a una función que lanza una excepción:
GetLockOwnerInfo(...)
ExecuteReader(...)
Esta función, a su vez, está llamando a una función que lanza una excepción:
GetLockOwnerInfo(...)
ExecuteReader(...)
ExecuteReader(...)
Y así:
GetLockOwnerInfo(...)
ExecuteReader(...)
ExecuteReader(...)
ExecuteReaderClient(...)
Fill(...)
Una de estas funciones lanza una SqlException
, aunque ese código no tiene idea de lo que es una SqlException
.
Los niveles más altos envuelven esa SqlException
en otra SqlException
BusinessRuleException
para incluir algunas propiedades especiales y detalles adicionales, mientras que incluyen la excepción "original" como InnerException
:
catch (DbException ex)
{
BusinessRuleExcpetion e = new BusinessRuleException(ex)
...
throw e;
}
Los niveles más altos envuelven esa LockerException
BusinessRuleException
en otra LockerException
para incluir algunas propiedades especiales y detalles adicionales, a la vez que incluyen la excepción "original" como InnerException
:
catch (BusinessRuleException ex)
{
LockerException e = new LockerException(ex)
...
throw e;
}
El problema ahora es que quiero capturar la SqlException SqlException
, para verificar un código de error en particular.
Pero no hay manera de "atrapar la excepción interna":
try
{
DoSomething();
}
catch (SqlException e)
{
if (e.Number = 247)
{
return "Someone";
}
else
throw;
}
Pensé en capturar la SqlException
cuando se lanzaba y copiar varios valores en la excepción de relanzamiento, pero ese código no depende de Sql. Está experimentando una SqlException
, pero no tiene ninguna dependencia en la excepción SqlException.
Pensé en atrapar todas las excepciones:
try
{
DoSomething(...);
}
catch (Exception e)
{
SqlException ex = HuntAroundForAnSqlException(e);
if (ex != null)
{
if (e.Number = 247)
{
return "Someone";
}
else
throw;
}
else
throw;
}
Pero eso es un código horrible.
Dado que .NET no le permite modificar el Message
de una Exception
para incluir información adicional, ¿cuál es el mecanismo previsto para detectar las excepciones originales?
Necesitas c # 6 / visual studio 2015 para hacer esto usando un predicado:
catch (ArgumentException e) when (e.ParamName == “…”)
{
}
Odio tener que decirte esto, pero no puedes atrapar una excepción interna.
Lo que puedes hacer es inspeccionar uno.
Le sugiero que capture su excepción de alto nivel (creo que fue LockerException
) e inspeccione la propiedad InnerException
de esa excepción. Verifique el tipo y, si no es una SqlException
, verifique la InnerException
de esa excepción. Camine cada uno hasta que encuentre un tipo de SqlException
, luego obtenga los datos que necesita.
Dicho esto, estoy de acuerdo con dasblinkenlight en que debe considerar, si es posible, un refactor pesado de su marco de excepción.
Tener una capa de aplicación externa preocupada por los detalles de una excepción envuelta es un olor a código; Cuanto más profundo es el envoltorio, más grande es el olor. La clase que ahora tiene envolviendo la SqlException
en una dbException
está probablemente diseñada para exponer un SqlClient como una interfaz de base de datos genérica. Como tal, esa clase debe incluir un medio para distinguir diferentes condiciones excepcionales. Puede, por ejemplo, definir una dbTimeoutWaitingForLockException y decidir lanzarla cuando atrapa una SqlException y determina, basándose en su código de error, que hubo un tiempo de espera de bloqueo. En vb.net, podría ser más limpio tener un tipo dbException que exponga una enumeración ErrorCause, por lo que se podría decir Catch Ex as dbException When ex.Cause = dbErrorCauses.LockTimeout
, pero desafortunadamente los filtros de excepción no se pueden usar en C #.
Si uno tiene una situación en la que la envoltura de la clase interna no sabrá lo suficiente sobre lo que está haciendo para saber cómo se deben asignar las excepciones, puede ser útil que el método de la clase interna acepte un delegado envolvente de la excepción, que tomaría una excepción. la clase interna ha atrapado o quisiera "tirar" y envolverla de una manera apropiada para la clase externa. Tal enfoque probablemente sería excesivo en los casos en que se llama a la clase interna directamente de la clase externa, pero puede ser útil si hay clases intermedias involucradas.
Verificar el código de error de una excepción envuelta no es una buena práctica, ya que daña bastante la encapsulación. Imagine en algún momento reescribir la lógica para leer desde una fuente que no sea de SQL, por ejemplo, un servicio web. Lanzaría algo distinto a SQLException
bajo la misma condición, y su código externo no tendría manera de detectarlo.
Debe agregar código al bloque que captura SQLException
para verificar e.Number = 247
ese mismo momento, y lanzar BusinessRuleException
con alguna propiedad que lo diferencie de BusinessRuleException
arrojado en respuesta a e.Number != 247
SQLException
y SQLException
con e.Number != 247
in alguna manera significativa Por ejemplo, si el número mágico 247
significa que has encontrado un duplicado (una pura especulación de mi parte en este momento), podrías hacer algo como esto:
catch (SQLException e) {
var toThrow = new BusinessRuleException(e);
if (e.Number == 247) {
toThrow.DuplicateDetected = true;
}
throw toThrow;
}
Cuando detecte BusinessRuleException
más tarde, puede verificar su propiedad DuplicateDetected
y actuar en consecuencia.
EDIT 1 (en respuesta al comentario de que el código de lectura de base de datos no puede verificar la SQLException
)
También puede cambiar su BusinessRuleException
para verificar la SQLException
en su constructor, de esta manera:
public BusinessRuleException(Exception inner)
: base(inner) {
SetDuplicateDetectedFlag(inner);
}
public BusinessRuleException(string message, Exception inner)
: base(message, inner) {
SetDuplicateDetectedFlag(inner);
}
private void SetDuplicateDetectedFlag(Exception inner) {
var innerSql = inner as SqlException;
DuplicateDetected = innerSql != null && innerSql.Number == 247;
}
Esto es menos deseable, porque rompe la encapsulación, pero al menos lo hace en un solo lugar. Si necesita examinar otros tipos de excepciones (por ejemplo, porque ha agregado una fuente de servicio web), podría agregarlo al método SetDuplicateDetectedFlag
y todo volvería a funcionar.