Creación e inicio de excepciones

Las excepciones se usan para indicar que se ha producido un error mientras se ejecutaba el programa. Se crean los objetos de excepción que describen un error y, a continuación, se inician con como throwinstrucción o expresión. Después, el tiempo de ejecución busca el controlador de excepciones más compatible.

Los programadores deberían producir excepciones cuando una o varias de las siguientes condiciones sean verdaderas:

  • El método no puede finalizar su función definida. Por ejemplo, si un parámetro de un método tiene un valor no válido:

    static void CopyObject(SampleClass original)
    {
        _ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original));
    }
    
  • Se realiza una llamada inadecuada a un objeto, en función del estado del objeto. Un ejemplo podría ser intentar escribir en un archivo de solo lectura. En los casos en los que un estado de objeto no permite una operación, genere una instancia de InvalidOperationException o un objeto con base en una derivación de esta clase. El código siguiente es un ejemplo de un método que genera un objeto InvalidOperationException:

    public class ProgramLog
    {
        FileStream logFile = null!;
        public void OpenLog(FileInfo fileName, FileMode mode) { }
    
        public void WriteLog()
        {
            if (!logFile.CanWrite)
            {
                throw new InvalidOperationException("Logfile cannot be read-only");
            }
            // Else write data to the log and return.
        }
    }
    
  • Cuando un argumento de un método genera una excepción. En este caso, se debe detectar la excepción original y se debe crear una instancia de ArgumentException. La excepción original debe pasarse al constructor de ArgumentException como el parámetro InnerException:

    static int GetValueFromArray(int[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch (IndexOutOfRangeException e)
        {
            throw new ArgumentOutOfRangeException(
                "Parameter index is out of range.", e);
        }
    }
    

    Nota:

    En el ejemplo anterior se muestra cómo usar la propiedad InnerException. Está simplificada intencionadamente. En la práctica, debe comprobar que un índice está en el intervalo antes de usarlo. Puede usar esta técnica de encapsular una excepción cuando un miembro de un parámetro produce una excepción que no se pudo prever antes de llamar al miembro.

Las excepciones contienen una propiedad denominada StackTrace. Esta cadena contiene el nombre de los métodos de la pila de llamadas actual, junto con el nombre de archivo y el número de la línea en la que se ha producido la excepción para cada método. Common Language Runtime (CLR) crea automáticamente un objeto StackTrace desde el punto de la instrucción throw, de manera que todas las excepciones se deben producir desde el punto en el que debe comenzar el seguimiento de la pila.

Todas las excepciones contienen una propiedad denominada Message. Esta cadena debe establecerse para que explique el motivo de la excepción. No se debe colocar información confidencial en materia de seguridad en el texto del mensaje. Además de Message, ArgumentException contiene una propiedad denominada ParamName que debe establecerse en el nombre del argumento que ha provocado que se genere la excepción. En un establecedor de propiedades, ParamName debe establecerse en value.

Los métodos públicos y protegidos generan excepciones siempre que no puedan finalizar sus funciones previstas. La clase de excepciones generada es la excepción más específica disponible que se ajuste a las condiciones de error. Estas excepciones se deben documentar como parte de la funcionalidad de la clase, y las clases derivadas o actualizaciones de la clase original deben mantener el mismo comportamiento para la compatibilidad con versiones anteriores.

Aspectos que se deben evitar al producir excepciones

En la siguiente lista se identifican los procedimientos que se deben evitar al producir excepciones:

  • No use excepciones para cambiar el flujo de un programa como parte de la ejecución normal. Use excepciones para notificar y controlar condiciones de error.
  • Las excepciones no se deben devolver como un parámetro o valor devuelto en lugar de producirse.
  • No genere System.Exception, System.SystemException, System.NullReferenceException ni System.IndexOutOfRangeException de manera intencionada desde su propio código fuente.
  • No cree excepciones que se puedan producir en el modo de depuración, pero no en el modo de lanzamiento. Para identificar los errores en tiempo de ejecución durante la fase de desarrollo, use la aserción de depuración.

Excepciones en métodos que devuelven tareas

Los métodos declarados con el modificador async tienen algunas consideraciones especiales cuando se trata de excepciones. Las excepciones iniciadas en un método async se almacenan en la tarea devuelta y no surgen hasta que, por ejemplo, se espera la tarea. Para obtener más información sobre las excepciones almacenadas, consulte Excepciones asincrónicas.

Se recomienda validar los argumentos e iniciar las excepciones correspondientes, como ArgumentException y ArgumentNullException, antes de escribir las partes asincrónicas de los métodos. Es decir, estas excepciones de validación deben surgir sincrónicamente antes de que se inicie el trabajo. El siguiente fragmento de código muestra un ejemplo en el que, si se inician las excepciones, las excepciones ArgumentException surgirían sincrónicamente, mientras que el InvalidOperationException se almacenaría en la tarea devuelta.

// Non-async, task-returning method.
// Within this method (but outside of the local function),
// any thrown exceptions emerge synchronously.
public static Task<Toast> ToastBreadAsync(int slices, int toastTime)
{
    if (slices is < 1 or > 4)
    {
        throw new ArgumentException(
            "You must specify between 1 and 4 slices of bread.",
            nameof(slices));
    }

    if (toastTime < 1)
    {
        throw new ArgumentException(
            "Toast time is too short.", nameof(toastTime));
    }

    return ToastBreadAsyncCore(slices, toastTime);

    // Local async function.
    // Within this function, any thrown exceptions are stored in the task.
    static async Task<Toast> ToastBreadAsyncCore(int slices, int time)
    {
        for (int slice = 0; slice < slices; slice++)
        {
            Console.WriteLine("Putting a slice of bread in the toaster");
        }
        // Start toasting.
        await Task.Delay(time);

        if (time > 2_000)
        {
            throw new InvalidOperationException("The toaster is on fire!");
        }

        Console.WriteLine("Toast is ready!");

        return new Toast();
    }
}

Definición de clases de excepción

Los programas pueden producir una clase de excepción predefinida en el espacio de nombres System (excepto en los casos indicados anteriormente) o crear sus propias clases de excepción mediante la derivación de Exception. Las clases derivadas deben definir al menos tres constructores: un constructor sin parámetros, uno que establezca la propiedad de mensaje y otro que establezca las propiedades Message y InnerException. Por ejemplo:

[Serializable]
public class InvalidDepartmentException : Exception
{
    public InvalidDepartmentException() : base() { }
    public InvalidDepartmentException(string message) : base(message) { }
    public InvalidDepartmentException(string message, Exception inner) : base(message, inner) { }
}

Agregue propiedades nuevas a la clase de excepción cuando los datos que proporcionan sean útiles para resolver la excepción. Si se agregan nuevas propiedades a la clase de excepción derivada, se debe invalidar ToString() para devolver la información agregada.

Especificación del lenguaje C#

Para obtener más información, vea las secciones Excepciones y La instrucción throw de la Especificación del lenguaje C#. La especificación del lenguaje es la fuente definitiva de la sintaxis y el uso de C#.

Vea también