Clase System.Exception

En este artículo se proporcionan comentarios adicionales a la documentación de referencia de esta API.

La Exception clase es la clase base para todas las excepciones. Cuando se produce un error, el sistema o la aplicación que se está ejecutando lo notifica iniciando una excepción que contiene información sobre el error. Después de iniciar una excepción, la controla la aplicación o el controlador de excepciones predeterminado.

Errores y excepciones

Los errores en tiempo de ejecución pueden producirse por diversos motivos. Sin embargo, no todos los errores se deben controlar como excepciones en el código. Estas son algunas categorías de errores que pueden producirse en tiempo de ejecución y las formas adecuadas de responder a ellos.

  • Errores de uso. Un error de uso representa un error en la lógica del programa que puede dar lugar a una excepción. Sin embargo, el error no debe solucionarse mediante el control de excepciones, sino modificando el código defectuoso. Por ejemplo, la invalidación del método Object.Equals(Object) en el ejemplo siguiente supone que el argumento obj siempre debe ser distinto de null.

    using System;
    
    public class Person1
    {
       private string _name;
    
       public string Name
       {
          get { return _name; }
          set { _name = value; }
       }
    
       public override int GetHashCode()
       {
          return this.Name.GetHashCode();
       }
    
       public override bool Equals(object obj)
       {
          // This implementation contains an error in program logic:
          // It assumes that the obj argument is not null.
          Person1 p = (Person1) obj;
          return this.Name.Equals(p.Name);
       }
    }
    
    public class UsageErrorsEx1
    {
       public static void Main()
       {
          Person1 p1 = new Person1();
          p1.Name = "John";
          Person1 p2 = null;
    
          // The following throws a NullReferenceException.
          Console.WriteLine("p1 = p2: {0}", p1.Equals(p2));
       }
    }
    
    // In F#, null is not a valid state for declared types 
    // without 'AllowNullLiteralAttribute'
    [<AllowNullLiteral>]
    type Person() =
        member val Name = "" with get, set
    
        override this.GetHashCode() =
            this.Name.GetHashCode()
    
        override this.Equals(obj) =
            // This implementation contains an error in program logic:
            // It assumes that the obj argument is not null.
            let p = obj :?> Person
            this.Name.Equals p.Name
    
    let p1 = Person()
    p1.Name <- "John"
    let p2: Person = null
    
    // The following throws a NullReferenceException.
    printfn $"p1 = p2: {p1.Equals p2}"
    
    Public Class Person
       Private _name As String
       
       Public Property Name As String
          Get
             Return _name
          End Get
          Set
             _name = value
          End Set
       End Property
       
       Public Overrides Function Equals(obj As Object) As Boolean
          ' This implementation contains an error in program logic:
          ' It assumes that the obj argument is not null.
          Dim p As Person = CType(obj, Person)
          Return Me.Name.Equals(p.Name)
       End Function
    End Class
    
    Module Example2
        Public Sub Main()
            Dim p1 As New Person()
            p1.Name = "John"
            Dim p2 As Person = Nothing
    
            ' The following throws a NullReferenceException.
            Console.WriteLine("p1 = p2: {0}", p1.Equals(p2))
        End Sub
    End Module
    

    La excepción NullReferenceException que da como resultado cuando obj es null se puede eliminar modificando el código fuente para probar explícitamente el valor null antes de llamar a la invalidación Object.Equals y, a continuación, volver a compilar. El ejemplo siguiente contiene el código fuente corregido que controla un argumento null.

    using System;
    
    public class Person2
    {
        private string _name;
    
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
    
        public override int GetHashCode()
        {
            return this.Name.GetHashCode();
        }
    
        public override bool Equals(object obj)
        {
            // This implementation handles a null obj argument.
            Person2 p = obj as Person2;
            if (p == null)
                return false;
            else
                return this.Name.Equals(p.Name);
        }
    }
    
    public class UsageErrorsEx2
    {
        public static void Main()
        {
            Person2 p1 = new Person2();
            p1.Name = "John";
            Person2 p2 = null;
    
            Console.WriteLine("p1 = p2: {0}", p1.Equals(p2));
        }
    }
    // The example displays the following output:
    //        p1 = p2: False
    
    // In F#, null is not a valid state for declared types 
    // without 'AllowNullLiteralAttribute'
    [<AllowNullLiteral>]
    type Person() =
        member val Name = "" with get, set
    
        override this.GetHashCode() =
            this.Name.GetHashCode()
    
        override this.Equals(obj) =
            // This implementation handles a null obj argument.
            match obj with
            | :? Person as p -> 
                this.Name.Equals p.Name
            | _ ->
                false
    
    let p1 = Person()
    p1.Name <- "John"
    let p2: Person = null
    
    printfn $"p1 = p2: {p1.Equals p2}"
    // The example displays the following output:
    //        p1 = p2: False
    
    Public Class Person2
        Private _name As String
    
        Public Property Name As String
            Get
                Return _name
            End Get
            Set
                _name = Value
            End Set
        End Property
    
        Public Overrides Function Equals(obj As Object) As Boolean
            ' This implementation handles a null obj argument.
            Dim p As Person2 = TryCast(obj, Person2)
            If p Is Nothing Then
                Return False
            Else
                Return Me.Name.Equals(p.Name)
            End If
        End Function
    End Class
    
    Module Example3
        Public Sub Main()
            Dim p1 As New Person2()
            p1.Name = "John"
            Dim p2 As Person2 = Nothing
    
            Console.WriteLine("p1 = p2: {0}", p1.Equals(p2))
        End Sub
    End Module
    ' The example displays the following output:
    '       p1 = p2: False
    

    En lugar de usar el control de excepciones para los errores de uso, puede usar el método Debug.Assert para identificar errores de uso en compilaciones de depuración y el método Trace.Assert para identificar errores de uso en compilaciones de depuración y de lanzamiento. Para obtener más información, vea Aserciones en el código administrado.

  • Errores del programa. Un error de programa es un error en tiempo de ejecución que no se puede evitar necesariamente escribiendo código sin errores.

    En algunos casos, un error de programa puede reflejar una condición de error esperada o rutinaria. En este caso, es posible que quiera evitar el uso del control de excepciones para tratar el error del programa y, en su lugar, reintentar la operación. Por ejemplo, si se espera que el usuario escriba una fecha en un formato determinado, puede analizar la cadena de fecha llamando al método DateTime.TryParseExact, que devuelve un valor Boolean que indica si la operación de análisis se realizó correctamente, en lugar de usar el método DateTime.ParseExact, que inicia una excepción FormatException si la cadena de fecha no se puede convertir en un valor DateTime. Del mismo modo, si un usuario intenta abrir un archivo que no existe, primero puede llamar al método File.Exists para comprobar si el archivo existe y, en caso contrario, preguntar al usuario si desea crearlo.

    En otros casos, un error de programa refleja una condición de error inesperada que se puede controlar en el código. Por ejemplo, incluso si ha comprobado que existe un archivo, puede que se elimine antes de que pueda abrirlo, o que esté dañado. En ese caso, al intentar abrir el archivo, crear instancias de un objeto StreamReader o llamar al método Open puede iniciar una excepción FileNotFoundException. En estos casos, debe usar el control de excepciones para recuperarse del error.

  • Errores del sistema. Un error del sistema es un error en tiempo de ejecución que no se puede controlar mediante programación de forma significativa. Por ejemplo, cualquier método puede iniciar una excepción OutOfMemoryException si Common Language Runtime no puede asignar memoria adicional. Normalmente, los errores del sistema no se controlan mediante el control de excepciones. En su lugar, puede usar un evento como AppDomain.UnhandledException y llamar al método Environment.FailFast para registrar la información de excepción y notificar al usuario el error antes de que finalice la aplicación.

Bloques Try/Catch

Common Language Runtime proporciona un modelo de control de excepciones basado en la representación de excepciones como objetos, y la separación del código de programa y el control de excepciones en bloques try y catch. Puede haber uno o varios bloques catch, cada uno diseñado para controlar un tipo determinado de excepción, o un bloque diseñado para detectar una excepción más específica que otro bloque.

Si una aplicación controla las excepciones que se producen durante la ejecución de un bloque de código de aplicación, el código debe colocarse dentro de una instrucción try y se denomina bloque try. El código de aplicación que controla las excepciones iniciadas por un bloque try se coloca dentro de una instrucción catch y se denomina bloque catch. Cero o más bloques catch están asociados a un bloque try y cada bloque catch incluye un filtro de tipo que determina los tipos de excepciones que controla.

Cuando se produce una excepción en un bloque try, el sistema busca en los bloques catch asociados en el orden en que aparecen en el código de la aplicación, hasta que encuentra un bloque catch que controla la excepción. Un bloque catch controla una excepción de tipo T si el filtro de tipo del bloque catch especifica T o cualquier tipo del que T derive. El sistema deja de buscar después de encontrar el primer bloque catch que controla la excepción. Por este motivo, en el código de la aplicación, se debe especificar un bloque catch que controle un tipo antes de un bloque catch que controle sus tipos base, como se muestra en el ejemplo que sigue a esta sección. Un bloque catch que controla System.Exception se especifica en último lugar.

Si ninguno de los bloques catch asociados con el bloque try actual controla la excepción y el bloque try actual se anida dentro de otros bloques try de la llamada actual, se buscan los bloques catch asociados con el siguiente bloque try envolvente. Si no se encuentra ningún bloque catch para la excepción, el sistema busca niveles de anidamiento anteriores en la llamada actual. Si no se encuentra ningún bloque catch para la excepción en la llamada actual, la excepción se pasa a la pila de llamadas y se busca en el marco de pila anterior un bloque catch que controle la excepción. La búsqueda de la pila de llamadas continúa hasta que se controla la excepción o hasta que no existen más fotogramas en la pila de llamadas. Si se alcanza la parte superior de la pila de llamadas sin encontrar un bloque catch que controla la excepción, el controlador de excepciones predeterminado la controla y la aplicación finaliza.

F# try..with (expresión)

F# no usa bloques catch. En su lugar, una excepción generada coincide con el patrón mediante un único bloque with. Como se trata de una expresión, en lugar de una instrucción, todas las rutas de acceso deben devolver el mismo tipo. Para obtener más información, consulte try...with (expresión).

Características del tipo de excepción

Los tipos de excepciones admiten las siguientes características:

  • Texto legible que describe el error. Cuando se produce una excepción, el tiempo de ejecución pone a disposición del usuario un mensaje de texto para informarle de la naturaleza del error y sugerirle una acción para resolver el problema. Este mensaje de texto se mantiene en la propiedad Message del objeto de excepción. Durante la creación del objeto de excepción, puede pasar una cadena de texto al constructor para describir los detalles de esa excepción determinada. Si no se proporciona ningún argumento de mensaje de error al constructor, se usa el mensaje de error predeterminado. Para obtener más información, vea la propiedad Message.

  • Estado de la pila de llamadas cuando se inició la excepción. La propiedad StackTrace contiene un seguimiento de la pila que se puede usar para determinar dónde se produce el error en el código. El seguimiento de la pila enumera todos los métodos llamados y los números de línea del archivo de origen donde se realizan las llamadas.

Propiedades de la clase de excepción

La clase Exception incluye una serie de propiedades que ayudan a identificar la ubicación del código, el tipo, el archivo de ayuda y el motivo de la excepción: StackTrace, InnerException, Message, HelpLink, HResult, Source, TargetSite y Data.

Cuando existe una relación causal entre dos o más excepciones, la propiedad InnerException mantiene esta información. La excepción externa se inicia en respuesta a esta excepción interna. El código que controla la excepción externa puede usar la información de la excepción interna anterior para controlar el error de forma más adecuada. La información complementaria sobre la excepción se puede almacenar como una colección de pares clave-valor en la propiedad Data.

La cadena de mensaje de error que se pasa al constructor durante la creación del objeto de excepción debe localizarse y se puede proporcionar desde un archivo de recursos mediante la clase ResourceManager. Para obtener más información sobre los recursos localizados, consulte los temas Crear ensamblados satélite y Empaquetar e implementar recursos.

Para proporcionar al usuario información extensa sobre por qué se produjo la excepción, la propiedad HelpLink puede contener una dirección URL (o URN) en un archivo de ayuda.

La clase Exception usa el HRESULT COR_E_EXCEPTION, que tiene el valor 0x80131500.

Para obtener una lista con los valores de propiedad iniciales de una instancia de la clase Exception, vea los constructores Exception.

Consideraciones de rendimiento

Al iniciar o controlar una excepción, se consume una cantidad significativa de recursos del sistema y tiempo de ejecución. Inicie excepciones solo para controlar condiciones verdaderamente extraordinarias, no para controlar eventos predecibles o el control de flujo. Por ejemplo, en algunos casos, como cuando se desarrolla una biblioteca de clases, es razonable iniciar una excepción si un argumento de método no es válido, ya que se espera que se llame al método con parámetros válidos. Un argumento de método no válido, si no es el resultado de un error de uso, significa que se ha producido algo extraordinario. Por el contrario, no inicie una excepción si la entrada del usuario no es válida, ya que puede esperar que los usuarios escriban ocasionalmente datos no válidos. En su lugar, proporcione un mecanismo de reintento para que los usuarios puedan escribir una entrada válida. Tampoco debe usar excepciones para controlar los errores de uso. En su lugar, use aserciones para identificar y corregir errores de uso.

Además, no inicie una excepción cuando un código de retorno sea suficiente; no convierta un código de retorno en una excepción; y no detecte rutinariamente una excepción, la omita y, a continuación, continúe procesando.

Volver a iniciar una excepción

En muchos casos, un controlador de excepciones simplemente quiere pasar la excepción al autor de la llamada. Esto suele ocurrir en:

  • Una biblioteca de clases que, a su vez, ajusta las llamadas a métodos de la biblioteca de clases de .NET u otras bibliotecas de clases.

  • Una aplicación o biblioteca que encuentra una excepción grave. El controlador de excepciones puede registrar la excepción y, a continuación, volver a iniciar la excepción.

La manera recomendada de volver a iniciar una excepción es simplemente usar la instrucción throw en C#, la función reraise en F#, y la instrucción Throw en Visual Basic sin incluir una expresión. Esto garantiza que toda la información de pila de llamadas se conserve cuando la excepción se propague al autor de la llamada. Esto se ilustra en el siguiente ejemplo: Un método de extensión de cadena, FindOccurrences, ajusta una o varias llamadas a String.IndexOf(String, Int32) sin validar sus argumentos de antemano.

using System;
using System.Collections.Generic;

public static class Library1
{
    public static int[] FindOccurrences(this String s, String f)
    {
        var indexes = new List<int>();
        int currentIndex = 0;
        try
        {
            while (currentIndex >= 0 && currentIndex < s.Length)
            {
                currentIndex = s.IndexOf(f, currentIndex);
                if (currentIndex >= 0)
                {
                    indexes.Add(currentIndex);
                    currentIndex++;
                }
            }
        }
        catch (ArgumentNullException)
        {
            // Perform some action here, such as logging this exception.

            throw;
        }
        return indexes.ToArray();
    }
}
open System

module Library = 
    let findOccurrences (s: string) (f: string) =
        let indexes = ResizeArray()
        let mutable currentIndex = 0
        try
            while currentIndex >= 0 && currentIndex < s.Length do
                currentIndex <- s.IndexOf(f, currentIndex)
                if currentIndex >= 0 then
                    indexes.Add currentIndex
                    currentIndex <- currentIndex + 1
        with :? ArgumentNullException ->
            // Perform some action here, such as logging this exception.
            reraise ()
        indexes.ToArray()
Imports System.Collections.Generic
Imports System.Runtime.CompilerServices

Public Module Library
    <Extension()>
    Public Function FindOccurrences1(s As String, f As String) As Integer()
        Dim indexes As New List(Of Integer)
        Dim currentIndex As Integer = 0
        Try
            Do While currentIndex >= 0 And currentIndex < s.Length
                currentIndex = s.IndexOf(f, currentIndex)
                If currentIndex >= 0 Then
                    indexes.Add(currentIndex)
                    currentIndex += 1
                End If
            Loop
        Catch e As ArgumentNullException
            ' Perform some action here, such as logging this exception.

            Throw
        End Try
        Return indexes.ToArray()
    End Function
End Module

A continuación, un autor de llamada llama a FindOccurrences dos veces. En la segunda llamada a FindOccurrences, el autor de la llamada pasa un null como cadena de búsqueda, lo que hace que el método String.IndexOf(String, Int32) inicie una excepción ArgumentNullException. El método FindOccurrences controla esta excepción y se pasa al autor de la llamada. Dado que la instrucción throw se usa sin expresión, la salida del ejemplo muestra que se conserva la pila de llamadas.

public class RethrowEx1
{
    public static void Main()
    {
        String s = "It was a cold day when...";
        int[] indexes = s.FindOccurrences("a");
        ShowOccurrences(s, "a", indexes);
        Console.WriteLine();

        String toFind = null;
        try
        {
            indexes = s.FindOccurrences(toFind);
            ShowOccurrences(s, toFind, indexes);
        }
        catch (ArgumentNullException e)
        {
            Console.WriteLine("An exception ({0}) occurred.",
                              e.GetType().Name);
            Console.WriteLine("Message:\n   {0}\n", e.Message);
            Console.WriteLine("Stack Trace:\n   {0}\n", e.StackTrace);
        }
    }

    private static void ShowOccurrences(String s, String toFind, int[] indexes)
    {
        Console.Write("'{0}' occurs at the following character positions: ",
                      toFind);
        for (int ctr = 0; ctr < indexes.Length; ctr++)
            Console.Write("{0}{1}", indexes[ctr],
                          ctr == indexes.Length - 1 ? "" : ", ");

        Console.WriteLine();
    }
}
// The example displays the following output:
//    'a' occurs at the following character positions: 4, 7, 15
//
//    An exception (ArgumentNullException) occurred.
//    Message:
//       Value cannot be null.
//    Parameter name: value
//
//    Stack Trace:
//          at System.String.IndexOf(String value, Int32 startIndex, Int32 count, Stri
//    ngComparison comparisonType)
//       at Library.FindOccurrences(String s, String f)
//       at Example.Main()
open Library

let showOccurrences toFind (indexes: int[]) =
    printf $"'{toFind}' occurs at the following character positions: "
    for i = 0 to indexes.Length - 1 do
        printf $"""{indexes[i]}{if i = indexes.Length - 1 then "" else ", "}"""
    printfn ""

let s = "It was a cold day when..."
let indexes = findOccurrences s "a"
showOccurrences "a" indexes
printfn ""

let toFind: string = null
try
    let indexes = findOccurrences s toFind
    showOccurrences toFind indexes

with :? ArgumentNullException as e ->
    printfn $"An exception ({e.GetType().Name}) occurred."
    printfn $"Message:\n   {e.Message}\n"
    printfn $"Stack Trace:\n   {e.StackTrace}\n"

// The example displays the following output:
//    'a' occurs at the following character positions: 4, 7, 15
//
//    An exception (ArgumentNullException) occurred.
//    Message:
//       Value cannot be null. (Parameter 'value')
//
//    Stack Trace:
//          at System.String.IndexOf(String value, Int32 startIndex, Int32 count, Stri
//    ngComparison comparisonType)
//       at Library.findOccurrences(String s, String f)
//       at <StartupCode$fs>.main@()
Module Example1
    Public Sub Main()
        Dim s As String = "It was a cold day when..."
        Dim indexes() As Integer = s.FindOccurrences1("a")
        ShowOccurrences(s, "a", indexes)
        Console.WriteLine()

        Dim toFind As String = Nothing
        Try
            indexes = s.FindOccurrences1(toFind)
            ShowOccurrences(s, toFind, indexes)
        Catch e As ArgumentNullException
            Console.WriteLine("An exception ({0}) occurred.",
                           e.GetType().Name)
            Console.WriteLine("Message:{0}   {1}{0}", vbCrLf, e.Message)
            Console.WriteLine("Stack Trace:{0}   {1}{0}", vbCrLf, e.StackTrace)
        End Try
    End Sub

    Private Sub ShowOccurrences(s As String, toFind As String, indexes As Integer())
        Console.Write("'{0}' occurs at the following character positions: ",
                    toFind)
        For ctr As Integer = 0 To indexes.Length - 1
            Console.Write("{0}{1}", indexes(ctr),
                       If(ctr = indexes.Length - 1, "", ", "))
        Next
        Console.WriteLine()
    End Sub
End Module
' The example displays the following output:
'    'a' occurs at the following character positions: 4, 7, 15
'
'    An exception (ArgumentNullException) occurred.
'    Message:
'       Value cannot be null.
'    Parameter name: value
'
'    Stack Trace:
'          at System.String.IndexOf(String value, Int32 startIndex, Int32 count, Stri
'    ngComparison comparisonType)
'       at Library.FindOccurrences(String s, String f)
'       at Example.Main()

En cambio, si se vuelve a iniciar la excepción mediante esta instrucción:

throw e;
Throw e
raise e

... a continuación, no se conserva la pila de llamadas completa y el ejemplo generaría el siguiente resultado:

'a' occurs at the following character positions: 4, 7, 15

An exception (ArgumentNullException) occurred.
Message:
   Value cannot be null.
Parameter name: value

Stack Trace:
      at Library.FindOccurrences(String s, String f)
   at Example.Main()

Una alternativa un poco más complicada es iniciar una nueva excepción y conservar la información de pila de llamadas de la excepción original en una excepción interna. A continuación, el autor de la llamada puede usar la propiedad InnerException de la nueva excepción para recuperar el marco de pila y otra información sobre la excepción original. En este caso, la instrucción throw es:

throw new ArgumentNullException("You must supply a search string.", e);
raise (ArgumentNullException("You must supply a search string.", e) )
Throw New ArgumentNullException("You must supply a search string.",
                             e)

El código de usuario que controla la excepción debe saber que la propiedad InnerException contiene información sobre la excepción original, como se muestra en el siguiente controlador de excepciones.

try
{
    indexes = s.FindOccurrences(toFind);
    ShowOccurrences(s, toFind, indexes);
}
catch (ArgumentNullException e)
{
    Console.WriteLine("An exception ({0}) occurred.",
                      e.GetType().Name);
    Console.WriteLine("   Message:\n{0}", e.Message);
    Console.WriteLine("   Stack Trace:\n   {0}", e.StackTrace);
    Exception ie = e.InnerException;
    if (ie != null)
    {
        Console.WriteLine("   The Inner Exception:");
        Console.WriteLine("      Exception Name: {0}", ie.GetType().Name);
        Console.WriteLine("      Message: {0}\n", ie.Message);
        Console.WriteLine("      Stack Trace:\n   {0}\n", ie.StackTrace);
    }
}
// The example displays the following output:
//    'a' occurs at the following character positions: 4, 7, 15
//
//    An exception (ArgumentNullException) occurred.
//       Message: You must supply a search string.
//
//       Stack Trace:
//          at Library.FindOccurrences(String s, String f)
//       at Example.Main()
//
//       The Inner Exception:
//          Exception Name: ArgumentNullException
//          Message: Value cannot be null.
//    Parameter name: value
//
//          Stack Trace:
//          at System.String.IndexOf(String value, Int32 startIndex, Int32 count, Stri
//    ngComparison comparisonType)
//       at Library.FindOccurrences(String s, String f)
try
    let indexes = findOccurrences s toFind
    showOccurrences toFind indexes
with :? ArgumentNullException as e ->
    printfn $"An exception ({e.GetType().Name}) occurred."
    printfn $"   Message:\n{e.Message}"
    printfn $"   Stack Trace:\n   {e.StackTrace}"
    let ie = e.InnerException
    if ie <> null then
        printfn "   The Inner Exception:"
        printfn $"      Exception Name: {ie.GetType().Name}"
        printfn $"      Message: {ie.Message}\n"
        printfn $"      Stack Trace:\n   {ie.StackTrace}\n"
// The example displays the following output:
//    'a' occurs at the following character positions: 4, 7, 15
//
//    An exception (ArgumentNullException) occurred.
//       Message: You must supply a search string.
//
//       Stack Trace:
//          at Library.FindOccurrences(String s, String f)
//       at Example.Main()
//
//       The Inner Exception:
//          Exception Name: ArgumentNullException
//          Message: Value cannot be null.
//    Parameter name: value
//
//          Stack Trace:
//          at System.String.IndexOf(String value, Int32 startIndex, Int32 count, Stri
//    ngComparison comparisonType)
//       at Library.FindOccurrences(String s, String f)
Try
    indexes = s.FindOccurrences(toFind)
    ShowOccurrences(s, toFind, indexes)
Catch e As ArgumentNullException
    Console.WriteLine("An exception ({0}) occurred.",
                   e.GetType().Name)
    Console.WriteLine("   Message: {1}{0}", vbCrLf, e.Message)
    Console.WriteLine("   Stack Trace:{0}   {1}{0}", vbCrLf, e.StackTrace)
    Dim ie As Exception = e.InnerException
    If ie IsNot Nothing Then
        Console.WriteLine("   The Inner Exception:")
        Console.WriteLine("      Exception Name: {0}", ie.GetType().Name)
        Console.WriteLine("      Message: {1}{0}", vbCrLf, ie.Message)
        Console.WriteLine("      Stack Trace:{0}   {1}{0}", vbCrLf, ie.StackTrace)
    End If
End Try
' The example displays the following output:
'       'a' occurs at the following character positions: 4, 7, 15
'
'       An exception (ArgumentNullException) occurred.
'          Message: You must supply a search string.
'
'          Stack Trace:
'             at Library.FindOccurrences(String s, String f)
'          at Example.Main()
'
'          The Inner Exception:
'             Exception Name: ArgumentNullException
'             Message: Value cannot be null.
'       Parameter name: value
'
'             Stack Trace:
'             at System.String.IndexOf(String value, Int32 startIndex, Int32 count, Stri
'       ngComparison comparisonType)
'          at Library.FindOccurrences(String s, String f)

Elegir excepciones estándar

Cuando tenga que iniciar una excepción, a menudo puede usar un tipo de excepción existente en .NET en lugar de implementar una excepción personalizada. Debe usar un tipo de excepción estándar en estas dos condiciones:

  • Está iniciando una excepción causada por un error de uso (es decir, por un error en la lógica del programa realizada por el desarrollador que llama al método). Normalmente, se iniciaría una excepción como ArgumentException, ArgumentNullException, InvalidOperationException o NotSupportedException. La cadena que se proporciona al constructor del objeto de excepción al crear una instancia del objeto de excepción debe describir el error para que el desarrollador pueda corregirlo. Para obtener más información, vea la propiedad Message.

  • Está controlando un error que se puede comunicar al autor de la llamada con una excepción de .NET existente. Debe iniciar la excepción más derivada posible. Por ejemplo, si un método requiere que un argumento sea un miembro válido de un tipo de enumeración, debe iniciar un InvalidEnumArgumentException (la clase más derivada) en lugar de un ArgumentException.

En la tabla siguiente se enumeran los tipos de excepción comunes y las condiciones en las que se iniciarían.

Exception Condición
ArgumentException Un argumento que no es NULL que se pasa a un método no es válido.
ArgumentNullException Un argumento que se pasa a un método es null.
ArgumentOutOfRangeException Un argumento está fuera del intervalo de valores válidos.
DirectoryNotFoundException Parte de una ruta de acceso de directorio no es válida.
DivideByZeroException El denominador de una operación de número entero o división Decimal es cero.
DriveNotFoundException Un controlador no está disponible o no existe.
FileNotFoundException No existe un archivo.
FormatException Un valor no tiene un formato adecuado para convertirse a partir de una cadena mediante un método de conversión como Parse.
IndexOutOfRangeException Un índice está fuera de los límites de una matriz o colección.
InvalidOperationException Una llamada de método no es válida en el estado actual de un objeto.
KeyNotFoundException No se encuentra la clave especificada para acceder a un miembro de una colección.
NotImplementedException No se implementa un método o una operación.
NotSupportedException No se admite un método o una operación.
ObjectDisposedException Se realiza una operación en un objeto que se ha eliminado.
OverflowException Una operación aritmética, de coerción o de conversión da como resultado un desbordamiento.
PathTooLongException Una ruta de acceso o nombre de archivo supera la longitud máxima definida por el sistema.
PlatformNotSupportedException La operación no es compatible con la plataforma actual.
RankException Una matriz con el número incorrecto de dimensiones se pasa a un método.
TimeoutException El intervalo de tiempo asignado a una operación ha expirado.
UriFormatException Se usa un identificador uniforme de recursos (URI) no válido.

Implementar excepciones personalizadas

En los casos siguientes, el uso de una excepción .NET existente para controlar una condición de error no es adecuado:

  • Cuando la excepción refleja un error de programa único que no se puede asignar a una excepción .NET existente.

  • Cuando la excepción requiere un control diferente del que es apropiado para una excepción .NET existente, o la excepción debe distinguirse de una excepción similar. Por ejemplo, si inicia una excepción ArgumentOutOfRangeException al analizar la representación numérica de una cadena que está fuera del intervalo del tipo entero de destino, no querrá usar la misma excepción para un error que resulte de que el autor de la llamada no proporcione los valores restringidos apropiados al llamar al método.

La clase Exception es la clase base de todas las excepciones de .NET. Muchas clases derivadas dependen del comportamiento heredado de los miembros de la clase Exception; no invalidan los miembros de Exception, ni definen ningún miembro único.

Para definir su propia clase de excepción:

  1. Defina una clase que herede de Exception. Si es necesario, defina los miembros únicos que necesite la clase para proporcionar información adicional sobre la excepción. Por ejemplo, la clase ArgumentException incluye una propiedad ParamName que especifica el nombre del parámetro cuyo argumento provocó la excepción, y la propiedad RegexMatchTimeoutException incluye una propiedad MatchTimeout que indica el intervalo de tiempo de espera.

  2. Si es necesario, invalide los miembros heredados cuya funcionalidad desea cambiar o modificar. Tenga en cuenta que la mayoría de las clases derivadas existentes de Exception no invalidan el comportamiento de los miembros heredados.

  3. Determine si el objeto de excepción personalizado es serializable. La serialización permite guardar información sobre la excepción y permite que un servidor y un proxy cliente compartan información de excepción en un contexto de comunicación remota. Para que el objeto de excepción sea serializable, márquelo con el atributo SerializableAttribute.

  4. Defina los constructores de la clase de excepción. Normalmente, las clases de excepción tienen uno o varios de los constructores siguientes:

    • Exception(), que usa valores predeterminados para inicializar las propiedades de un nuevo objeto de excepción.

    • Exception(String), que inicializa un nuevo objeto de excepción con un mensaje de error especificado.

    • Exception(String, Exception), que inicializa un nuevo objeto de excepción con un mensaje de error especificado y una excepción interna.

    • Exception(SerializationInfo, StreamingContext), que es un constructor protected que inicializa un nuevo objeto de excepción a partir de datos serializados. Debe implementar este constructor si ha elegido hacer serializable el objeto de excepción.

En el ejemplo siguiente se muestra el uso de una clase de excepción personalizada. Define una excepción NotPrimeException que se inicia cuando un cliente intenta recuperar una secuencia de números primos especificando un número inicial que no es primo. La excepción define una nueva propiedad, NonPrime, que devuelve el número no primo que provocó la excepción. Además de implementar un constructor sin parámetros protegido y un constructor con parámetros SerializationInfo y StreamingContext para la serialización, la clase NotPrimeException define tres constructores adicionales para admitir la propiedad NonPrime. Cada constructor llama a un constructor de clase base además de conservar el valor del número no primo. La clase NotPrimeException también se marca con el atributo SerializableAttribute.

using System;
using System.Runtime.Serialization;

[Serializable()]
public class NotPrimeException : Exception
{
   private int notAPrime;

   protected NotPrimeException()
      : base()
   { }

   public NotPrimeException(int value) :
      base(String.Format("{0} is not a prime number.", value))
   {
      notAPrime = value;
   }

   public NotPrimeException(int value, string message)
      : base(message)
   {
      notAPrime = value;
   }

   public NotPrimeException(int value, string message, Exception innerException) :
      base(message, innerException)
   {
      notAPrime = value;
   }

   protected NotPrimeException(SerializationInfo info,
                               StreamingContext context)
      : base(info, context)
   { }

   public int NonPrime
   { get { return notAPrime; } }
}
namespace global

open System
open System.Runtime.Serialization

[<Serializable>]
type NotPrimeException = 
    inherit Exception
    val notAPrime: int

    member this.NonPrime =
        this.notAPrime

    new (value) =
        { inherit Exception($"%i{value} is not a prime number."); notAPrime = value }

    new (value, message) =
        { inherit Exception(message); notAPrime = value }

    new (value, message, innerException: Exception) =
        { inherit Exception(message, innerException); notAPrime = value }

    // F# does not support protected members
    new () = 
        { inherit Exception(); notAPrime = 0 }

    new (info: SerializationInfo, context: StreamingContext) =
        { inherit Exception(info, context); notAPrime = 0 }
Imports System.Runtime.Serialization

<Serializable()> _
Public Class NotPrimeException : Inherits Exception
   Private notAPrime As Integer

   Protected Sub New()
      MyBase.New()
   End Sub

   Public Sub New(value As Integer)
      MyBase.New(String.Format("{0} is not a prime number.", value))
      notAPrime = value
   End Sub

   Public Sub New(value As Integer, message As String)
      MyBase.New(message)
      notAPrime = value
   End Sub

   Public Sub New(value As Integer, message As String, innerException As Exception)
      MyBase.New(message, innerException)
      notAPrime = value
   End Sub

   Protected Sub New(info As SerializationInfo,
                     context As StreamingContext)
      MyBase.New(info, context)
   End Sub

   Public ReadOnly Property NonPrime As Integer
      Get
         Return notAPrime
      End Get
   End Property
End Class

La clase PrimeNumberGenerator que se muestra en el ejemplo siguiente usa la criba de Eratóstenes para calcular la secuencia de números primos desde 2 hasta un límite especificado por el cliente en la llamada a su constructor de clase. El método GetPrimesFrom devuelve todos los números primos que son mayores o iguales a un límite inferior especificado, pero produce un NotPrimeException si ese límite inferior no es un número primo.

using System;
using System.Collections.Generic;

[Serializable]
public class PrimeNumberGenerator
{
   private const int START = 2;
   private int maxUpperBound = 10000000;
   private int upperBound;
   private bool[] primeTable;
   private List<int> primes = new List<int>();

   public PrimeNumberGenerator(int upperBound)
   {
      if (upperBound > maxUpperBound)
      {
         string message = String.Format(
                           "{0} exceeds the maximum upper bound of {1}.",
                           upperBound, maxUpperBound);
         throw new ArgumentOutOfRangeException(message);
      }
      this.upperBound = upperBound;
      // Create array and mark 0, 1 as not prime (True).
      primeTable = new bool[upperBound + 1];
      primeTable[0] = true;
      primeTable[1] = true;

      // Use Sieve of Eratosthenes to determine prime numbers.
      for (int ctr = START; ctr <= (int)Math.Ceiling(Math.Sqrt(upperBound));
            ctr++)
      {
         if (primeTable[ctr]) continue;

         for (int multiplier = ctr; multiplier <= upperBound / ctr; multiplier++)
            if (ctr * multiplier <= upperBound) primeTable[ctr * multiplier] = true;
      }
      // Populate array with prime number information.
      int index = START;
      while (index != -1)
      {
         index = Array.FindIndex(primeTable, index, (flag) => !flag);
         if (index >= 1)
         {
            primes.Add(index);
            index++;
         }
      }
   }

   public int[] GetAllPrimes()
   {
      return primes.ToArray();
   }

   public int[] GetPrimesFrom(int prime)
   {
      int start = primes.FindIndex((value) => value == prime);
      if (start < 0)
         throw new NotPrimeException(prime, String.Format("{0} is not a prime number.", prime));
      else
         return primes.FindAll((value) => value >= prime).ToArray();
   }
}
namespace global

open System

[<Serializable>]
type PrimeNumberGenerator(upperBound) =
    let start = 2
    let maxUpperBound = 10000000
    let primes = ResizeArray()
    let primeTable = 
        upperBound + 1
        |> Array.zeroCreate<bool>

    do
        if upperBound > maxUpperBound then
            let message = $"{upperBound} exceeds the maximum upper bound of {maxUpperBound}."
            raise (ArgumentOutOfRangeException message)
        
        // Create array and mark 0, 1 as not prime (True).
        primeTable[0] <- true
        primeTable[1] <- true

        // Use Sieve of Eratosthenes to determine prime numbers.
        for i = start to float upperBound |> sqrt |> ceil |> int do
            if not primeTable[i] then
                for multiplier = i to upperBound / i do
                    if i * multiplier <= upperBound then
                        primeTable[i * multiplier] <- true
        
        // Populate array with prime number information.
        let mutable index = start
        while index <> -1 do
            index <- Array.FindIndex(primeTable, index, fun flag -> not flag)
            if index >= 1 then
                primes.Add index
                index <- index + 1

    member _.GetAllPrimes() =
        primes.ToArray()

    member _.GetPrimesFrom(prime) =
        let start = 
            Seq.findIndex ((=) prime) primes
        
        if start < 0 then
            raise (NotPrimeException(prime, $"{prime} is not a prime number.") )
        else
            Seq.filter ((>=) prime) primes
            |> Seq.toArray
Imports System.Collections.Generic

<Serializable()> Public Class PrimeNumberGenerator
   Private Const START As Integer = 2
   Private maxUpperBound As Integer = 10000000
   Private upperBound As Integer
   Private primeTable() As Boolean
   Private primes As New List(Of Integer)

   Public Sub New(upperBound As Integer)
      If upperBound > maxUpperBound Then
         Dim message As String = String.Format(
             "{0} exceeds the maximum upper bound of {1}.",
             upperBound, maxUpperBound)
         Throw New ArgumentOutOfRangeException(message)
      End If
      Me.upperBound = upperBound
      ' Create array and mark 0, 1 as not prime (True).
      ReDim primeTable(upperBound)
      primeTable(0) = True
      primeTable(1) = True

      ' Use Sieve of Eratosthenes to determine prime numbers.
      For ctr As Integer = START To CInt(Math.Ceiling(Math.Sqrt(upperBound)))
         If primeTable(ctr) Then Continue For

         For multiplier As Integer = ctr To CInt(upperBound \ ctr)
            If ctr * multiplier <= upperBound Then primeTable(ctr * multiplier) = True
         Next
      Next
      ' Populate array with prime number information.
      Dim index As Integer = START
      Do While index <> -1
         index = Array.FindIndex(primeTable, index, Function(flag)
                                                       Return Not flag
                                                    End Function)
         If index >= 1 Then
            primes.Add(index)
            index += 1
         End If
      Loop
   End Sub

   Public Function GetAllPrimes() As Integer()
      Return primes.ToArray()
   End Function

   Public Function GetPrimesFrom(prime As Integer) As Integer()
      Dim start As Integer = primes.FindIndex(Function(value)
                                                 Return value = prime
                                              End Function)
      If start < 0 Then
         Throw New NotPrimeException(prime, String.Format("{0} is not a prime number.", prime))
      Else
         Return primes.FindAll(Function(value)
                                  Return value >= prime
                               End Function).ToArray()
      End If
   End Function
End Class

En el ejemplo siguiente se realizan dos llamadas al método GetPrimesFrom con números no primos, uno de los cuales cruza los límites del dominio de aplicación. En ambos casos, la excepción se inicia y se controla correctamente en el código de cliente.

using System;
using System.Reflection;

class Example1
{
    public static void Main()
    {
        int limit = 10000000;
        PrimeNumberGenerator primes = new PrimeNumberGenerator(limit);
        int start = 1000001;
        try
        {
            int[] values = primes.GetPrimesFrom(start);
            Console.WriteLine("There are {0} prime numbers from {1} to {2}",
                              start, limit);
        }
        catch (NotPrimeException e)
        {
            Console.WriteLine("{0} is not prime", e.NonPrime);
            Console.WriteLine(e);
            Console.WriteLine("--------");
        }

        AppDomain domain = AppDomain.CreateDomain("Domain2");
        PrimeNumberGenerator gen = (PrimeNumberGenerator)domain.CreateInstanceAndUnwrap(
                                          typeof(Example).Assembly.FullName,
                                          "PrimeNumberGenerator", true,
                                          BindingFlags.Default, null,
                                          new object[] { 1000000 }, null, null);
        try
        {
            start = 100;
            Console.WriteLine(gen.GetPrimesFrom(start));
        }
        catch (NotPrimeException e)
        {
            Console.WriteLine("{0} is not prime", e.NonPrime);
            Console.WriteLine(e);
            Console.WriteLine("--------");
        }
    }
}
open System
open System.Reflection

let limit = 10000000
let primes = PrimeNumberGenerator limit
let start = 1000001
try
    let values = primes.GetPrimesFrom start
    printfn $"There are {values.Length} prime numbers from {start} to {limit}"
with :? NotPrimeException as e ->
    printfn $"{e.NonPrime} is not prime"
    printfn $"{e}"
    printfn "--------"

let domain = AppDomain.CreateDomain "Domain2"
let gen = 
    domain.CreateInstanceAndUnwrap(
        typeof<PrimeNumberGenerator>.Assembly.FullName,
        "PrimeNumberGenerator", true,
        BindingFlags.Default, null,
        [| box 1000000 |], null, null)
    :?> PrimeNumberGenerator
try
    let start = 100
    printfn $"{gen.GetPrimesFrom start}"
with :? NotPrimeException as e ->
    printfn $"{e.NonPrime} is not prime"
    printfn $"{e}"
    printfn "--------"
Imports System.Reflection

Module Example
   Sub Main()
      Dim limit As Integer = 10000000
      Dim primes As New PrimeNumberGenerator(limit)
      Dim start As Integer = 1000001
      Try
         Dim values() As Integer = primes.GetPrimesFrom(start)
         Console.WriteLine("There are {0} prime numbers from {1} to {2}",
                           start, limit)
      Catch e As NotPrimeException
         Console.WriteLine("{0} is not prime", e.NonPrime)
         Console.WriteLine(e)
         Console.WriteLine("--------")
      End Try

      Dim domain As AppDomain = AppDomain.CreateDomain("Domain2")
      Dim gen As PrimeNumberGenerator = domain.CreateInstanceAndUnwrap(
                                        GetType(Example).Assembly.FullName,
                                        "PrimeNumberGenerator", True,
                                        BindingFlags.Default, Nothing,
                                        {1000000}, Nothing, Nothing)
      Try
         start = 100
         Console.WriteLine(gen.GetPrimesFrom(start))
      Catch e As NotPrimeException
         Console.WriteLine("{0} is not prime", e.NonPrime)
         Console.WriteLine(e)
         Console.WriteLine("--------")
      End Try
   End Sub
End Module
' The example displays the following output:
'      1000001 is not prime
'      NotPrimeException: 1000001 is not a prime number.
'         at PrimeNumberGenerator.GetPrimesFrom(Int32 prime)
'         at Example.Main()
'      --------
'      100 is not prime
'      NotPrimeException: 100 is not a prime number.
'         at PrimeNumberGenerator.GetPrimesFrom(Int32 prime)
'         at Example.Main()
'      --------

Ejemplos

En el ejemplo siguiente se muestra un bloque catch (with en F#) que se define para controlar los errores ArithmeticException. Este bloque catch también detecta errores DivideByZeroException, ya que DivideByZeroException deriva de ArithmeticException y no hay ningún bloque catch definido explícitamente para errores DivideByZeroException.

using System;

class ExceptionTestClass
{
   public static void Main()
   {
      int x = 0;
      try
      {
         int y = 100 / x;
      }
      catch (ArithmeticException e)
      {
         Console.WriteLine($"ArithmeticException Handler: {e}");
      }
      catch (Exception e)
      {
         Console.WriteLine($"Generic Exception Handler: {e}");
      }
   }	
}
/*
This code example produces the following results:

ArithmeticException Handler: System.DivideByZeroException: Attempted to divide by zero.
   at ExceptionTestClass.Main()

*/
module ExceptionTestModule

open System

let x = 0
try
    let y = 100 / x
    ()
with
| :? ArithmeticException as e ->
    printfn $"ArithmeticException Handler: {e}"
| e ->
    printfn $"Generic Exception Handler: {e}"

// This code example produces the following results:
//     ArithmeticException Handler: System.DivideByZeroException: Attempted to divide by zero.
//        at <StartupCode$fs>.$ExceptionTestModule.main@()
Class ExceptionTestClass
   
   Public Shared Sub Main()
      Dim x As Integer = 0
      Try
         Dim y As Integer = 100 / x
      Catch e As ArithmeticException
         Console.WriteLine("ArithmeticException Handler: {0}", e.ToString())
      Catch e As Exception
         Console.WriteLine("Generic Exception Handler: {0}", e.ToString())
      End Try
   End Sub
End Class
'
'This code example produces the following results:
'
'ArithmeticException Handler: System.OverflowException: Arithmetic operation resulted in an overflow.
'   at ExceptionTestClass.Main()
'