Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
El control de excepciones adecuado es esencial para la confiabilidad de las aplicaciones. Puedes controlar intencionadamente las excepciones esperadas para evitar que la aplicación se bloquee. Sin embargo, una aplicación fallida es más confiable y diagnosticable que una aplicación con un comportamiento indefinido.
En este artículo se describen los procedimientos recomendados para controlar y crear excepciones.
Control de excepciones
Los siguientes procedimientos recomendados se refieren a cómo controlar las excepciones:
- Uso de bloques try/catch/finally para recuperarse de errores o liberar recursos
- Control de condiciones comunes para evitar excepciones
- Detectar excepciones de cancelación y asincrónicas
- Diseñar clases para que se puedan evitar excepciones
- Estado de restauración cuando los métodos no se completan debido a excepciones
- Capturar y volver a iniciar excepciones correctamente
Uso de bloques try/catch/finally para recuperarse de errores o liberar recursos
Para el código que puede generar una excepción y, cuando la aplicación pueda recuperarse de esa excepción, use try
/catch
bloques alrededor del código. En catch
bloques, ordene siempre las excepciones del más derivado al menos derivado. (Todas las excepciones derivan de la clase Exception. Las excepciones más derivadas no se manejan mediante una cláusula catch
que es precedida por una cláusula catch
para una clase de excepción base). Cuando el código no se pueda recuperar de una excepción, no capture esa excepción. Permita que los métodos más arriba en la pila de llamadas se recuperen si es posible.
Limpie los recursos asignados con instrucciones using
o bloques finally
. Se prefieren instrucciones using
para limpiar automáticamente los recursos cuando se producen excepciones. Use bloques finally
para limpiar los recursos que no implementan IDisposable. El código de una finally
cláusula casi siempre se ejecuta incluso cuando se producen excepciones.
Manejar condiciones comunes para evitar excepciones
En el caso de las condiciones que es probable que se produzcan, pero que podrían desencadenar una excepción, considere la posibilidad de controlarlas de una manera que evite la excepción. Por ejemplo, si intenta cerrar una conexión que ya está cerrada, obtendrá un InvalidOperationException
. Puede evitarlo mediante una if
instrucción para comprobar el estado de conexión antes de intentar cerrarlo.
if (conn.State != ConnectionState.Closed)
{
conn.Close();
}
If conn.State <> ConnectionState.Closed Then
conn.Close()
End IF
Si no comprueba el estado de conexión antes de cerrar, puede detectar la InvalidOperationException
excepción.
try
{
conn.Close();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.GetType().FullName);
Console.WriteLine(ex.Message);
}
Try
conn.Close()
Catch ex As InvalidOperationException
Console.WriteLine(ex.GetType().FullName)
Console.WriteLine(ex.Message)
End Try
El enfoque que se debe elegir depende de la frecuencia con la que se espera que se produzca el evento.
Use el control de excepciones si el evento no se produce a menudo, es decir, si el evento es realmente excepcional e indica un error, como un final de archivo inesperado. Cuando se usa el control de excepciones, se ejecuta menos código en condiciones normales.
Compruebe si hay condiciones de error en el código si el evento se produce de forma rutinaria y podría considerarse parte de la ejecución normal. Al comprobar si hay condiciones de error comunes, se ejecuta menos código porque evita excepciones.
Nota:
Las comprobaciones iniciales eliminan las excepciones la mayor parte del tiempo. Sin embargo, puede haber condiciones de competencia en las que la condición protegida cambie entre la comprobación y la operación y, en ese caso, todavía podría producirse una excepción.
Llamar a los métodos Try*
para evitar excepciones
Si el costo de rendimiento de las excepciones es prohibitivo, algunos métodos de biblioteca de .NET proporcionan formas alternativas de control de errores. Por ejemplo, Int32.Parse lanza un OverflowException si el valor que se va a analizar es demasiado grande para ser representado por Int32. Sin embargo, Int32.TryParse no produce esta excepción. En su lugar, devuelve un valor Boolean y tiene un out
parámetro que contiene el entero válido analizado tras la operación correcta.
Dictionary<TKey,TValue>.TryGetValue tiene un comportamiento similar para intentar obtener un valor de un diccionario.
Detectar excepciones de cancelación y asincrónicas
Es mejor detectar OperationCanceledException en lugar de TaskCanceledException, que deriva de OperationCanceledException
, cuando se llama a un método asincrónico. Muchos métodos asincrónicos producen una OperationCanceledException excepción si se solicita la cancelación. Estas excepciones permiten detener la ejecución de forma eficaz y desenredar la pila de llamadas una vez que se observa una solicitud de cancelación.
Los métodos asincrónicos almacenan excepciones que se producen durante la ejecución en la tarea que devuelven. Si se almacena una excepción en la tarea devuelta, se producirá esa excepción cuando se espere la tarea. Las excepciones de uso, como ArgumentException, todavía se producen sincrónicamente. Para obtener más información, vea Excepciones asincrónicas.
Diseñar clases para que se puedan evitar excepciones
Una clase puede proporcionar métodos o propiedades que le permiten evitar realizar una llamada que desencadenaría una excepción. Por ejemplo, la FileStream clase proporciona métodos que ayudan a determinar si se ha alcanzado el final del archivo. Puede llamar a estos métodos para evitar la excepción que se produce si lee más allá del final del archivo. En el ejemplo siguiente se muestra cómo leer al final de un archivo sin desencadenar una excepción:
class FileRead
{
public static void ReadAll(FileStream fileToRead)
{
ArgumentNullException.ThrowIfNull(fileToRead);
int b;
// Set the stream position to the beginning of the file.
fileToRead.Seek(0, SeekOrigin.Begin);
// Read each byte to the end of the file.
for (int i = 0; i < fileToRead.Length; i++)
{
b = fileToRead.ReadByte();
Console.Write(b.ToString());
// Or do something else with the byte.
}
}
}
Class FileRead
Public Sub ReadAll(fileToRead As FileStream)
' This if statement is optional
' as it is very unlikely that
' the stream would ever be null.
If fileToRead Is Nothing Then
Throw New System.ArgumentNullException()
End If
Dim b As Integer
' Set the stream position to the beginning of the file.
fileToRead.Seek(0, SeekOrigin.Begin)
' Read each byte to the end of the file.
For i As Integer = 0 To fileToRead.Length - 1
b = fileToRead.ReadByte()
Console.Write(b.ToString())
' Or do something else with the byte.
Next i
End Sub
End Class
Otra manera de evitar excepciones es devolver null
(o valor predeterminado) para los casos de error más comunes en lugar de producir una excepción. Un caso de error común se puede considerar un flujo de control normal. Al devolver null
(o valor predeterminado) en estos casos, se minimiza el impacto en el rendimiento de una aplicación.
En el caso de los tipos de valor, considere si se debe usar Nullable<T>
o default
como indicador de error para la aplicación. Al usar Nullable<Guid>
, default
se convierte null
en en lugar de Guid.Empty
. A veces, agregar Nullable<T>
puede hacer que sea más claro cuando un valor está presente o ausente. Otras veces, agregar Nullable<T>
puede crear casos adicionales para comprobar que no son necesarios y que solo sirven para crear posibles orígenes de errores.
Restaurar el estado si los métodos no se completan debido a excepciones
Los autores de llamadas deben poder asumir que no se producen efectos no deseados cuando se produce una excepción desde un método. Por ejemplo, si tiene código que transfiere dinero retirando de una cuenta y depositando en otra cuenta, y se produce una excepción al ejecutar el depósito, no desea que la retirada permanezca en vigor.
public void TransferFunds(Account from, Account to, decimal amount)
{
from.Withdrawal(amount);
// If the deposit fails, the withdrawal shouldn't remain in effect.
to.Deposit(amount);
}
Public Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)
from.Withdrawal(amount)
' If the deposit fails, the withdrawal shouldn't remain in effect.
[to].Deposit(amount)
End Sub
El método anterior no lanza directamente ninguna excepción. Sin embargo, debe escribir el método para que se revierta el retiro si se produce un error en la operación de depósito.
Una manera de controlar esta situación es detectar las excepciones producidas por la transacción de depósito y revertir la retirada.
private static void TransferFunds(Account from, Account to, decimal amount)
{
string withdrawalTrxID = from.Withdrawal(amount);
try
{
to.Deposit(amount);
}
catch
{
from.RollbackTransaction(withdrawalTrxID);
throw;
}
}
Private Shared Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)
Dim withdrawalTrxID As String = from.Withdrawal(amount)
Try
[to].Deposit(amount)
Catch
from.RollbackTransaction(withdrawalTrxID)
Throw
End Try
End Sub
En este ejemplo se muestra el uso de throw
para relanzar la excepción original, lo que facilita a los que hacen las llamadas ver la causa real del problema sin tener que examinar la propiedad InnerException. Una alternativa consiste en iniciar una nueva excepción e incluir la excepción original como excepción interna.
catch (Exception ex)
{
from.RollbackTransaction(withdrawalTrxID);
throw new TransferFundsException("Withdrawal failed.", innerException: ex)
{
From = from,
To = to,
Amount = amount
};
}
Catch ex As Exception
from.RollbackTransaction(withdrawalTrxID)
Throw New TransferFundsException("Withdrawal failed.", innerException:=ex) With
{
.From = from,
.[To] = [to],
.Amount = amount
}
End Try
Capturar y volver a iniciar excepciones correctamente
Cuando se inicia una excepción, parte de la información que contiene es el seguimiento de la pila. El seguimiento de la pila es una lista de la jerarquía de llamadas de método que comienza con el método que inicia la excepción y termina con el que la captura. Si lanzas de nuevo una excepción especificando la excepción en la throw
instrucción, por ejemplo, throw e
, el seguimiento de la pila se reinicia en el método actual y se pierde la lista de llamadas de método entre el método original que produjo la excepción y el método actual. Para mantener la información de seguimiento de la pila original con la excepción, hay dos opciones que dependen de la ubicación desde la que vuelve a iniciar la excepción:
- Si vuelve a iniciar la excepción desde el controlador (bloque
catch
) que ha detectado la instancia de excepción, use la instrucciónthrow
sin especificar la excepción. La regla de análisis de código CA2200 ayuda a buscar ubicaciones en el código donde podría perder accidentalmente la información de seguimiento de la pila. - Si vuelve a generar la excepción desde un lugar distinto del controlador (bloque
catch
), use ExceptionDispatchInfo.Capture(Exception) para capturar la excepción en el controlador y ExceptionDispatchInfo.Throw() cuando quiera volver a generarla. Puede usar la ExceptionDispatchInfo.SourceException propiedad para inspeccionar la excepción capturada.
En el ejemplo siguiente se muestra cómo se puede usar la ExceptionDispatchInfo clase y cuál podría ser el aspecto de la salida.
ExceptionDispatchInfo? edi = null;
try
{
var txt = File.ReadAllText(@"C:\temp\file.txt");
}
catch (FileNotFoundException e)
{
edi = ExceptionDispatchInfo.Capture(e);
}
// ...
Console.WriteLine("I was here.");
if (edi is not null)
edi.Throw();
Si el archivo del código de ejemplo no existe, se genera la siguiente salida:
I was here.
Unhandled exception. System.IO.FileNotFoundException: Could not find file 'C:\temp\file.txt'.
File name: 'C:\temp\file.txt'
at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)
at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize)
at System.IO.File.ReadAllText(String path, Encoding encoding)
at Example.ProcessFile.Main() in C:\repos\ConsoleApp1\Program.cs:line 12
--- End of stack trace from previous location ---
at Example.ProcessFile.Main() in C:\repos\ConsoleApp1\Program.cs:line 24
Lanzar excepciones
Los siguientes procedimientos recomendados se refieren a cómo se producen excepciones:
- Uso de tipos de excepciones predefinidos
- Uso de métodos del generador de excepciones
- Incluir un mensaje de cadena localizado
- Uso de la gramática adecuada
- Colocar instrucciones throw correctamente
- No emitir excepciones en cláusulas finally
- No plantees excepciones desde lugares inesperados
- Iniciar excepciones de validación de argumentos de forma sincrónica
Uso de tipos de excepciones predefinidos
Introduce una nueva clase de excepción solo cuando no se aplica una predefinida. Por ejemplo:
- Si una llamada a un conjunto de propiedades o método no es adecuada según el estado actual del objeto, inicie una InvalidOperationException excepción.
- Si se pasan parámetros no válidos, lance una ArgumentException excepción o una de las clases predefinidas que derivan de ArgumentException.
Nota:
Aunque es mejor usar tipos de excepción predefinidos siempre que sea posible, no debe generar algunos tipos de excepción reservados, como AccessViolationException, IndexOutOfRangeExceptionNullReferenceException y StackOverflowException. Para obtener más información, vea CA2201: No generar tipos de excepción reservados.
Uso de métodos del generador de excepciones
Es habitual que una clase lance la misma excepción desde diferentes lugares en su implementación. Para evitar un código excesivo, cree un método auxiliar que cree la excepción y lo devuelva. Por ejemplo:
class FileReader
{
private readonly string _fileName;
public FileReader(string path)
{
_fileName = path;
}
public byte[] Read(int bytes)
{
byte[] results = FileUtils.ReadFromFile(_fileName, bytes) ?? throw NewFileIOException();
return results;
}
static FileReaderException NewFileIOException()
{
string description = "My NewFileIOException Description";
return new FileReaderException(description);
}
}
Class FileReader
Private fileName As String
Public Sub New(path As String)
fileName = path
End Sub
Public Function Read(bytes As Integer) As Byte()
Dim results() As Byte = FileUtils.ReadFromFile(fileName, bytes)
If results Is Nothing
Throw NewFileIOException()
End If
Return results
End Function
Function NewFileIOException() As FileReaderException
Dim description As String = "My NewFileIOException Description"
Return New FileReaderException(description)
End Function
End Class
Algunos tipos clave de excepción de .NET tienen métodos auxiliares estáticos throw
que asignan y lanzan la excepción. Debe llamar a estos métodos en lugar de construir e iniciar el tipo de excepción correspondiente:
- ArgumentNullException.ThrowIfNull
- ArgumentException.ThrowIfNullOrEmpty(String, String)
- ArgumentException.ThrowIfNullOrWhiteSpace(String, String)
- ArgumentOutOfRangeException.ThrowIfZero<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfNegative<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfLessThan<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfNotEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfNegativeOrZero<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfGreaterThan<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfLessThanOrEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<T>(T, T, String)
- ObjectDisposedException.ThrowIf
Sugerencia
Las siguientes reglas de análisis de código pueden ayudarle a encontrar lugares en el código donde puede aprovechar estas asistentes estáticas throw
: CA1510, CA1511, CA1512 y CA1513.
Si va a implementar un método asincrónico, llame a CancellationToken.ThrowIfCancellationRequested() en vez de comprobar si se solicitó la cancelación y luego construir y lanzar OperationCanceledException. Para obtener más información, consulte CA2250.
Incluir un mensaje de cadena localizado
El mensaje de error que ve el usuario se deriva de la propiedad de la Exception.Message excepción que se produjo y no del nombre de la clase de excepción. Normalmente, se asigna un valor a la Exception.Message propiedad pasando la cadena de mensaje al message
argumento de un constructor Exception.
En el caso de las aplicaciones localizadas, debe proporcionar una cadena de mensaje localizada para cada excepción que pueda producir la aplicación. Los archivos de recursos se usan para proporcionar mensajes de error localizados. Para obtener información sobre cómo localizar aplicaciones y recuperar cadenas localizadas, consulte los siguientes artículos:
- Cómo: Crear excepciones definidas por el usuario con mensajes de excepción localizados
- Recursos en aplicaciones .NET
- System.Resources.ResourceManager
Uso de la gramática adecuada
Escriba oraciones claras e incluya puntuación final. Cada frase de la cadena asignada a la Exception.Message propiedad debe terminar en un punto. Por ejemplo, "La tabla de registro se ha desbordado". Usa la gramática y puntuación correctas.
Colocar instrucciones throw correctamente
Coloque instrucciones throw donde el seguimiento de la pila resulte útil. El seguimiento de pila comienza en la instrucción en que se produce la excepción y termina en la instrucción catch
que detecta la excepción.
No emitir excepciones en cláusulas finally
No genere excepciones en las cláusulas finally
. Para obtener más información, consulte la regla de análisis de código CA2219.
No emitir excepciones desde ubicaciones inesperadas
Algunos métodos, como Equals
, GetHashCode
, y ToString
, así como los constructores estáticos y operadores de igualdad, no deben producir excepciones. Para obtener más información, consulte la regla de análisis de código CA1065.
Iniciar excepciones de validación de argumentos de forma sincrónica
En los métodos que devuelven tareas, debe validar los argumentos y lanzar las excepciones correspondientes, como ArgumentException y ArgumentNullException, antes de entrar en la parte asincrónica del método. Las excepciones iniciadas en la parte asincrónica del método se almacenan en la tarea devuelta y no surgen hasta que, por ejemplo, se espera la tarea. Para obtener más información, vea Excepciones en métodos que devuelven tareas.
Tipos de excepciones personalizados
Los procedimientos recomendados siguientes se refieren a los tipos de excepciones personalizados:
-
Finalizar nombres de clase de excepción con
Exception
- Incluir tres constructores
- Proporcione propiedades adicionales según sea necesario.
Finalizar nombres de clase de excepción con Exception
Cuando se necesite una excepción personalizada, debe ponerse el nombre apropiado y derivarla de la clase Exception. Por ejemplo:
public class MyFileNotFoundException : Exception
{
}
Public Class MyFileNotFoundException
Inherits Exception
End Class
Incluir tres constructores
Use al menos los tres constructores comunes al crear sus propias clases de excepción: el constructor sin parámetros, un constructor que toma un mensaje de cadena y un constructor que toma un mensaje de cadena y una excepción interna.
- Exception(), que usa valores predeterminados.
- Exception(String), que acepta un mensaje de cadena.
- Exception(String, Exception), que acepta un mensaje de cadena y una excepción interna.
Para obtener un ejemplo, vea Cómo: Crear excepciones definidas por el usuario.
Proporcione propiedades adicionales según sea necesario.
Proporcione propiedades adicionales para una excepción (además de la cadena de mensaje personalizada) solo cuando haya un escenario de programación en el que la información adicional sea útil. Por ejemplo, FileNotFoundException proporciona la FileName propiedad .