¿Encuentra la excepción más interna sin usar un bucle while?

82

Cuando C # lanza una excepción, puede tener una excepción interna. Lo que quiero hacer es obtener la excepción más interna, o en otras palabras, la excepción de hoja que no tiene una excepción interna. Puedo hacer esto en un ciclo while:

while (e.InnerException != null)
{
    e = e.InnerException;
}

Pero me preguntaba si habría alguna frase que pudiera utilizar para hacer esto.

Daniel T.
fuente
4
Estoy lanzando una excepción en una de mis clases, pero mi clase está siendo utilizada por una biblioteca que se traga todas las excepciones y lanza la suya propia. El problema es que la excepción de la biblioteca es muy genérica y necesito saber qué excepción específica lancé para saber cómo manejar el problema. Para empeorar las cosas, la biblioteca lanzará su propia excepción varias veces anidadas entre sí, a una profundidad arbitraria. Entonces, por ejemplo, arrojará LibraryException -> LibraryException -> LibraryException -> MyException. Mi excepción es siempre la última de la cadena y no tiene su propia excepción interna.
Daniel T.
Oh, entiendo por qué podrías bajar a lo más interno, habiéndolo hecho yo mismo (y variantes, como lo más interno que se deriva de un tipo en particular) pero no entiendo cuál es el problema con lo que ya tienes.
Jon Hanna
Oh, veo lo que quieres decir. Solo me preguntaba si había otra forma de escribirlo. Me he acostumbrado tanto a usar LINQpara hacer casi todo lo que parece extraño cuando tengo que escribir un bucle. Trabajo principalmente con acceso a datos, por lo que prácticamente trabajo con ICollectionscasi todo lo que hago.
Daniel T.
@Daniel, ¿no se trata LINQsolo de enmascarar el hecho de que el código todavía tiene que repetirse? ¿Existen verdaderos comandos basados ​​en conjuntos en .NET?
Brad
6
La respuesta más correcta está al acecho cerca de la parte inferior con (actualmente) solo 2 votos: Exception.GetBaseException (). Esto ha estado en el marco básicamente desde siempre. Gracias a batCattle por el control de cordura.
Jay

Respuestas:

138

Un trazador de líneas :)

while (e.InnerException != null) e = e.InnerException;

Obviamente, no se puede hacer más simple.

Como dijo Glenn McElhoe en esta respuesta , es la única forma confiable.

Draco Ater
fuente
7
El método GetBaseException () hace esto sin un bucle. msdn.microsoft.com/en-us/library/…
codingoutloud
8
Esto funciona mientras Exception.GetBaseException()que para mí no lo hizo.
Tarik
121

Creo que Exception.GetBaseException()hace lo mismo que estas soluciones.

Advertencia: a partir de varios comentarios, hemos descubierto que no siempre hace literalmente lo mismo y, en algunos casos, la solución recursiva / iterativa lo llevará más lejos. Por lo general, es la excepción más interna, que es decepcionantemente inconsistente, gracias a ciertos tipos de Excepciones que anulan las predeterminadas. Sin embargo, si detecta tipos específicos de excepciones y se asegura razonablemente de que no sean bichos raros (como AggregateException), esperaría que obtenga la excepción legítima más interna / más temprana.

Josh Sutterfield
fuente
3
+1 No hay nada como conocer el BCL para evitar soluciones complicadas. Claramente, esta es la respuesta más correcta.
Jay
4
Por alguna razón, GetBaseException()no devolvió la primera excepción de raíz.
Tarik
4
Glenn McElhoe señaló que, de hecho, GetBaseException () no siempre hace lo que MSDN sugiere que podemos esperar en general (que "la excepción que es la causa raíz de una o más excepciones posteriores"). En AggregateException se limita a la excepción más baja del mismo tipo, y quizás haya otras.
Josh Sutterfield
1
No funciona para mi problema. Tengo algunos niveles más por debajo de lo que esto proporciona.
Giovanni B
2
GetBaseException()no funcionó para mí, porque la excepción más alta es una AggregateException.
Chris Ballance
21

Pasar por InnerExceptions es la única forma confiable.

Si la excepción detectada es una AggregateException, GetBaseException()solo devuelve la AggregateException más interna.

http://msdn.microsoft.com/en-us/library/system.aggregateexception.getbaseexception.aspx

Glenn McElhoe
fuente
1
Buena atrapada. Gracias, eso explica los comentarios anteriores donde esa respuesta no funcionó para algunas personas. Esto deja en claro que la descripción general de MSDN de la "causa raíz de una o más excepciones posteriores" no siempre es lo que supondría, ya que las clases derivadas pueden anularla. La regla real parece ser que todas las excepciones en una cadena de excepciones "concuerdan" en GetBaseException () y devuelven el mismo objeto, que parece ser el caso de AggregateException.
Josh Sutterfield
12

Si no sabe qué tan profundo están anidadas las excepciones internas, no hay forma de evitar un bucle o recursividad.

Por supuesto, puede definir un método de extensión que abstraiga esto:

public static class ExceptionExtensions
{
    public static Exception GetInnermostException(this Exception e)
    {
        if (e == null)
        {
            throw new ArgumentNullException("e");
        }

        while (e.InnerException != null)
        {
            e = e.InnerException;
        }

        return e;
    }
}
dtb
fuente
2

A veces, puede tener muchas excepciones internas (muchas excepciones burbujeadas). En cuyo caso, es posible que desee hacer:

List<Exception> es = new List<Exception>();
while(e.InnerException != null)
{
   es.add(e.InnerException);
   e = e.InnerException
}
Ryan Ternier
fuente
1

No es una línea, pero está cerca:

        Func<Exception, Exception> last = null;
        last = e => e.InnerException == null ? e : last(e.InnerException);
Steve Ellinger
fuente
1

De hecho, es tan simple que podrías usar Exception.GetBaseException()

Try
      //Your code
Catch ex As Exception
      MessageBox.Show(ex.GetBaseException().Message, My.Settings.MsgBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
End Try
armadillo.mx
fuente
¡Gracias por tu solución! Poca gente piensa en VB: D
Federico Navarrete
1
¡Y hay una razón por la que! : P
Yoda
1

Tienes que hacer un bucle, y al tener que hacerlo, es más limpio mover el bucle a una función separada.

Creé un método de extensión para lidiar con esto. Devuelve una lista de todas las excepciones internas del tipo especificado, persiguiendo Exception.InnerException y AggregateException.InnerExceptions.

En mi problema particular, perseguir las excepciones internas fue más complicado de lo habitual, porque las excepciones las lanzaban los constructores de clases que se invocaban mediante la reflexión. La excepción que estábamos detectando tenía una InnerException de tipo TargetInvocationException, y las excepciones que realmente necesitábamos mirar estaban enterradas en lo profundo del árbol.

public static class ExceptionExtensions
{
    public static IEnumerable<T> innerExceptions<T>(this Exception ex)
        where T : Exception
    {
        var rVal = new List<T>();

        Action<Exception> lambda = null;
        lambda = (x) =>
        {
            var xt = x as T;
            if (xt != null)
                rVal.Add(xt);

            if (x.InnerException != null)
                lambda(x.InnerException);

            var ax = x as AggregateException;
            if (ax != null)
            {
                foreach (var aix in ax.InnerExceptions)
                    lambda(aix);
            }
        };

        lambda(ex);

        return rVal;
    }
}

El uso es bastante simple. Si, por ejemplo, desea saber si encontramos un

catch (Exception ex)
{
    var myExes = ex.innerExceptions<MyException>();
    if (myExes.Any(x => x.Message.StartsWith("Encountered my specific error")))
    {
        // ...
    }
}
Jeff Dege
fuente
¿De qué se trata Exception.GetBaseException()?
Kiquenet
1

Puede usar la recursividad para crear un método en una clase de utilidad en algún lugar.

public Exception GetFirstException(Exception ex)
{
    if(ex.InnerException == null) { return ex; } // end case
    else { return GetFirstException(ex.InnerException); } // recurse
}

Utilizar:

try
{
    // some code here
}
catch (Exception ex)
{
    Exception baseException = GetFirstException(ex);
}

El método de extensión sugerido (buena idea @dtb)

public static Exception GetFirstException(this Exception ex)
{
    if(ex.InnerException == null) { return ex; } // end case
    else { return GetFirstException(ex.InnerException); } // recurse
}

Utilizar:

try
{
    // some code here
}
catch (Exception ex)
{
    Exception baseException = ex.GetFirstException();
}
Puntilla
fuente
Útil public static Exception GetOriginalException(this Exception ex) { if (ex.InnerException == null) return ex; return ex.InnerException.GetOriginalException(); }
Kiquenet
¿No aplica AggregateException.InnerExceptions?
Kiquenet
0

Otra forma de hacerlo es llamando GetBaseException()dos veces:

Exception innermostException = e.GetBaseException().GetBaseException();

Esto funciona porque si es un AggregateException, la primera llamada lo lleva al no más interno, AggregateExceptionluego la segunda llamada lo lleva a la excepción más interna de esa excepción. Si la primera excepción no es an AggregateException, entonces la segunda llamada simplemente devuelve la misma excepción.

JoeySchentrup
fuente
0

Me encontré con esto y quería poder enumerar todos los mensajes de excepción de la "pila" de excepciones. Entonces, se me ocurrió esto.

public static string GetExceptionMessages(Exception ex)
{
    if (ex.InnerException is null)
        return ex.Message;
    else return $"{ex.Message}\n{GetExceptionMessages(ex.InnerException)}";
}
Ken Lamb
fuente