Finden Sie die innerste Ausnahme, ohne eine while-Schleife zu verwenden?

82

Wenn C # eine Ausnahme auslöst, kann es eine innere Ausnahme geben. Was ich tun möchte, ist die innerste Ausnahme zu erhalten, oder mit anderen Worten, die Blattausnahme, die keine innere Ausnahme hat. Ich kann dies in einer while-Schleife tun:

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

Aber ich fragte mich, ob es einen Einzeiler gab, mit dem ich dies stattdessen tun könnte.

Daniel T.
quelle
4
Ich löse eine Ausnahme in einer meiner Klassen aus, aber meine Klasse wird von einer Bibliothek verwendet, die alle Ausnahmen verschluckt und ihre eigenen auslöst. Das Problem ist, dass die Bibliotheksausnahme sehr allgemein ist und ich wissen muss, welche spezifische Ausnahme ich ausgelöst habe, um zu wissen, wie das Problem behandelt werden soll. Um die Sache noch schlimmer zu machen, wird die Bibliothek ihre eigene Ausnahme mehrmals ineinander verschachtelt bis zu einer beliebigen Tiefe auslösen. So wird es zum Beispiel werfen LibraryException -> LibraryException -> LibraryException -> MyException. Meine Ausnahme ist immer die letzte in der Kette und hat keine eigene innere Ausnahme.
Daniel T.
Oh, ich verstehe, warum Sie vielleicht ins Innerste zurücktreten, nachdem Sie dies selbst getan haben (und Varianten, wie das Innerste, die von einem bestimmten Typ stammen), aber ich verstehe nicht, worum es bei dem geht, was Sie bereits haben.
Jon Hanna
Oh, ich verstehe was du meinst. Ich habe mich nur gefragt, ob es eine andere Art gibt, es zu schreiben. Ich habe mich so daran gewöhnt LINQ, fast alles zu tun, dass es komisch erscheint, wenn ich eine Schleife schreiben muss. Ich arbeite hauptsächlich mit Datenzugriff, also arbeite ich so ziemlich mit ICollectionsfast allem, was ich tue.
Daniel T.
@ Daniel, LINQmaskiert nicht nur die Tatsache, dass der Code im Wesentlichen noch eine Schleife haben muss? Gibt es in .NET echte satzbasierte Befehle?
Brad
6
Die richtigste Antwort lauert mit (derzeit) nur 2 Stimmen ganz unten - Exception.GetBaseException (). Dies war im Grunde für immer im Rahmen. Vielen Dank an batCattle für den Sanity Check.
Jay

Antworten:

138

Einzeiler :)

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

Einfacher geht es natürlich nicht.

Wie in dieser Antwort von Glenn McElhoe gesagt, ist dies der einzig zuverlässige Weg.

Draco Ater
quelle
7
Die GetBaseException () -Methode führt diese w / oa-Schleife aus. msdn.microsoft.com/en-us/library/…
Codierungoutloud
8
Das funktioniert Exception.GetBaseException()bei mir nicht.
Tarik
121

Ich glaube Exception.GetBaseException(), das Gleiche wie diese Lösungen.

Vorsichtsmaßnahme: Aus verschiedenen Kommentaren haben wir herausgefunden, dass es nicht immer buchstäblich dasselbe tut, und in einigen Fällen bringt Sie die rekursive / iterierende Lösung weiter. Dies ist normalerweise die innerste Ausnahme, die dank bestimmter Arten von Ausnahmen, die die Standardeinstellung überschreiben, enttäuschend inkonsistent ist. Wenn Sie jedoch bestimmte Arten von Ausnahmen abfangen und einigermaßen sicherstellen, dass es sich nicht um seltsame Kugeln handelt (wie AggregateException), würde ich erwarten, dass die legitime innerste / früheste Ausnahme vorliegt.

Josh Sutterfield
quelle
3
+1 Es gibt nichts Schöneres, als die BCL zu kennen, um komplizierte Lösungen zu vermeiden. Dies ist eindeutig die richtigste Antwort.
Jay
4
Aus irgendeinem Grund wurde GetBaseException()die erste Root-Ausnahme nicht zurückgegeben.
Tarik
4
Glenn McElhoe wies darauf hin, dass GetBaseException () in der Tat nicht immer das tut, was MSDN im Allgemeinen erwarten kann (dass "die Ausnahme die Hauptursache für eine oder mehrere nachfolgende Ausnahmen ist"). In AggregateException ist es auf die niedrigste Ausnahme desselben Typs beschränkt, und möglicherweise gibt es andere.
Josh Sutterfield
1
Funktioniert nicht für mein Problem. Ich habe ein paar Stufen mehr als das, was dies bietet.
Giovanni B
2
GetBaseException()hat bei mir nicht funktioniert, da die oberste Ausnahme eine AggregateException ist.
Chris Ballance
21

Das Durchlaufen von InnerExceptions ist der einzig zuverlässige Weg.

Wenn die abgefangene Ausnahme eine AggregateException ist, wird GetBaseException()nur die innerste AggregateException zurückgegeben.

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

Glenn McElhoe
quelle
1
Guter Fang. Danke - das erklärt die obigen Kommentare, in denen diese Antwort für einige Leute nicht funktioniert hat. Dies macht deutlich, dass die allgemeine Beschreibung von MSDN als "Grundursache für eine oder mehrere nachfolgende Ausnahmen" nicht immer das ist, was Sie annehmen würden, da abgeleitete Klassen sie möglicherweise überschreiben. Die eigentliche Regel scheint zu sein, dass alle Ausnahmen in einer Ausnahmekette auf GetBaseException () "übereinstimmen" und dasselbe Objekt zurückgeben, was bei AggregateException der Fall zu sein scheint.
Josh Sutterfield
12

Wenn Sie nicht wissen, wie tief die inneren Ausnahmen verschachtelt sind, führt kein Weg an einer Schleife oder Rekursion vorbei.

Natürlich können Sie eine Erweiterungsmethode definieren, die dies abstrahiert:

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
quelle
2

Manchmal haben Sie viele innere Ausnahmen (viele sprudelnde Ausnahmen). In diesem Fall möchten Sie möglicherweise Folgendes tun:

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

Nicht ganz eine Zeile, aber nah:

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

In der Tat ist so einfach, dass Sie verwenden könnten 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
quelle
Vielen Dank für Ihre Lösung! Nur wenige Leute denken in VB: D
Federico Navarrete
1
Und es gibt einen Grund warum! : P
Yoda
1

Sie müssen eine Schleife erstellen, und es ist sauberer, die Schleife in eine separate Funktion zu verschieben.

Ich habe eine Erweiterungsmethode erstellt, um damit umzugehen. Es gibt eine Liste aller inneren Ausnahmen des angegebenen Typs zurück und verfolgt Exception.InnerException und AggregateException.InnerExceptions.

In meinem speziellen Problem war das Verfolgen der inneren Ausnahmen komplizierter als gewöhnlich, da die Ausnahmen von den Konstruktoren von Klassen ausgelöst wurden, die durch Reflexion aufgerufen wurden. Die Ausnahme, die wir abfingen, hatte eine InnerException vom Typ TargetInvocationException, und die Ausnahmen, die wir tatsächlich betrachten mussten, waren tief im Baum vergraben.

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;
    }
}

Die Verwendung ist ziemlich einfach. Wenn Sie zum Beispiel wissen möchten, ob wir auf a gestoßen sind

catch (Exception ex)
{
    var myExes = ex.innerExceptions<MyException>();
    if (myExes.Any(x => x.Message.StartsWith("Encountered my specific error")))
    {
        // ...
    }
}
Jeff Dege
quelle
Was ist mit Exception.GetBaseException()?
Kiquenet
1

Sie können die Rekursion verwenden, um irgendwo eine Methode in einer Dienstprogrammklasse zu erstellen.

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

Verwenden:

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

Die vorgeschlagene Erweiterungsmethode (gute Idee @dtb)

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

Verwenden:

try
{
    // some code here
}
catch (Exception ex)
{
    Exception baseException = ex.GetFirstException();
}
Brad
quelle
Nützlich public static Exception GetOriginalException(this Exception ex) { if (ex.InnerException == null) return ex; return ex.InnerException.GetOriginalException(); }
Kiquenet
Nicht zutreffend AggregateException.InnerExceptions?
Kiquenet
0

Sie können dies auch tun, indem Sie GetBaseException()zweimal anrufen :

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

Dies funktioniert, weil Sie, wenn es sich um eine handelt AggregateException, beim ersten Anruf zum innersten Nicht- AggregateExceptionAnruf gelangen und beim zweiten Anruf zur innersten Ausnahme dieser Ausnahme gelangen. Wenn die erste Ausnahme keine ist AggregateException, gibt der zweite Aufruf nur dieselbe Ausnahme zurück.

JoeySchentrup
quelle
0

Ich bin darauf gestoßen und wollte in der Lage sein, alle Ausnahmemeldungen aus dem Ausnahmestapel aufzulisten. Also habe ich mir das ausgedacht.

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