Znaleźć najbardziej wewnętrzny wyjątek bez używania pętli while?

84

Gdy C # zgłasza wyjątek, może mieć wyjątek wewnętrzny. Chcę uzyskać najbardziej wewnętrzny wyjątek lub innymi słowy wyjątek liścia, który nie ma wewnętrznego wyjątku. Mogę to zrobić w pętli while:

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

Ale zastanawiałem się, czy jest jakaś jedna linijka, której mógłbym użyć zamiast tego.

Daniel T.
źródło
4
Rzucam wyjątek w jednej z moich zajęć, ale moja klasa jest używana przez bibliotekę, która połyka wszystkie wyjątki i zgłasza własne. Problem w tym, że wyjątek w bibliotece jest bardzo ogólny i muszę wiedzieć, który konkretny wyjątek rzuciłem, aby wiedzieć, jak rozwiązać problem. Co gorsza, biblioteka wyrzuci swój własny wyjątek wiele razy zagnieżdżony w sobie, na dowolną głębokość. Więc na przykład rzuci LibraryException -> LibraryException -> LibraryException -> MyException. Mój wyjątek jest zawsze ostatni w łańcuchu i nie ma własnego wewnętrznego wyjątku.
Daniel T.
Och, rozumiem, dlaczego możesz zejść do najbardziej wewnętrznego, zrobiłem to sam (i warianty, takie jak najbardziej wewnętrzne, które wywodzą się z określonego typu), ale nie rozumiem, na czym polega problem z tym, co już masz.
Jon Hanna
Och, rozumiem, co masz na myśli. Zastanawiałem się tylko, czy istnieje inny sposób napisania tego. Tak się przyzwyczaiłem do LINQrobienia prawie wszystkiego, że wydaje mi się dziwne, kiedy muszę napisać pętlę. Pracuję głównie z dostępem do danych, więc ICollectionsprawie wszystko, co robię.
Daniel T.
@Daniel, czy nie maskujesz LINQtylko faktu, że kod nadal zasadniczo się zapętla ? Czy są jakieś polecenia oparte na zestawie w .NET?
Brad
6
Najbardziej poprawna odpowiedź czai się na samym dole z (obecnie) tylko 2 głosami - Exception.GetBaseException (). To było w ramach w zasadzie od zawsze. Dzięki batCattle za sprawdzenie poczytalności.
Jay

Odpowiedzi:

139

Oneliner :)

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

Oczywiście nie możesz tego uprościć.

Jak powiedział w tej odpowiedzi Glenn McElhoe, jest to jedyny niezawodny sposób.

Draco Ater
źródło
7
Metoda GetBaseException () robi to bez pętli. msdn.microsoft.com/en-us/library/…
codingoutloud
8
To działa, Exception.GetBaseException()ale nie dla mnie.
Tarik
122

Uważam, że Exception.GetBaseException()robi to samo, co te rozwiązania.

Uwaga: z różnych komentarzy wywnioskowaliśmy, że nie zawsze robi to dosłownie to samo, aw niektórych przypadkach rekurencyjne / iteracyjne rozwiązanie zaprowadzi Cię dalej. Jest to zwykle najgłębsza wyjątek, który jest niestety niespójne, dzięki niektórych typów wyjątków, które zastąpić domyślny. Jeśli jednak wyłapiesz określone typy wyjątków i upewnisz się, że nie są to dziwactwa (jak AggregateException), spodziewałbym się, że otrzyma on najbardziej wewnętrzny / najwcześniejszy wyjątek.

Josh Sutterfield
źródło
3
+1 Nie ma to jak znajomość BCL w celu uniknięcia zawiłych rozwiązań. Oczywiście jest to najbardziej poprawna odpowiedź.
Jay
4
Z jakiegoś powodu GetBaseException()nie zwrócił pierwszego wyjątku głównego.
Tarik
4
Glenn McElhoe zwrócił uwagę, że rzeczywiście GetBaseException () nie zawsze robi to, co sugeruje MSDN, czego możemy się ogólnie spodziewać („Wyjątek będący główną przyczyną jednego lub więcej kolejnych wyjątków”). W AggregateException jest ograniczony do najniższego wyjątku tego samego typu i być może istnieją inne.
Josh Sutterfield,
1
Nie działa w moim przypadku. Mam kilka poziomów niższych niż to, co zapewnia.
Giovanni B
2
GetBaseException()nie zadziałało dla mnie, ponieważ najwyższy wyjątek to AggregateException.
Chris Ballance,
22

Jedynym niezawodnym sposobem jest pętla przez InnerExceptions.

Jeśli przechwycony wyjątek jest AggregateException, GetBaseException()zwraca tylko najbardziej wewnętrzny AggregateException.

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

Glenn McElhoe
źródło
1
Dobry chwyt. Dzięki - to wyjaśnia powyższe komentarze, w których ta odpowiedź nie działała dla niektórych osób. To wyjaśnia, że ​​ogólny opis MSDN dotyczący „głównej przyczyny jednego lub więcej kolejnych wyjątków” nie zawsze jest tym, co można by przypuszczać, ponieważ klasy pochodne mogą go zastąpić. Prawdziwą regułą wydaje się być to, że wszystkie wyjątki w łańcuchu wyjątków „zgadzają się” na GetBaseException () i zwracają ten sam obiekt, co wydaje się mieć miejsce w przypadku AggregateException.
Josh Sutterfield,
12

Jeśli nie wiesz, jak głęboko zagnieżdżone są wewnętrzne wyjątki, nie ma sposobu na obejście pętli lub rekursji.

Oczywiście możesz zdefiniować metodę rozszerzenia, która to odejmuje:

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
źródło
2

Czasami możesz mieć wiele wewnętrznych wyjątków (wiele wyjątków w formie bąbelków). W takim przypadku możesz chcieć:

List<Exception> es = new List<Exception>();
while(e.InnerException != null)
{
   es.add(e.InnerException);
   e = e.InnerException
}
Ryan Ternier
źródło
1

Nie całkiem jedna linia, ale blisko:

        Func<Exception, Exception> last = null;
        last = e => e.InnerException == null ? e : last(e.InnerException);
Steve Ellinger
źródło
1

W rzeczywistości jest tak prosty, że możesz go użyć 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
źródło
Dzięki za rozwiązanie! Niewiele osób myśli w VB: D
Federico Navarrete
1
I jest powód, dla którego! : P
Yoda,
1

Musisz zapętlić, a pętla jest czystsza, aby przenieść pętlę do oddzielnej funkcji.

Stworzyłem metodę rozszerzenia, aby sobie z tym poradzić. Zwraca listę wszystkich wyjątków wewnętrznych określonego typu, ścigając Exception.InnerException i AggregateException.InnerExceptions.

W moim konkretnym problemie pogoń za wewnętrznymi wyjątkami było bardziej skomplikowane niż zwykle, ponieważ wyjątki były rzucane przez konstruktorów klas, które były wywoływane przez refleksję. Wyjątek, który łapaliśmy, miał wyjątek InnerException typu TargetInvocationException, a wyjątki, którym faktycznie musieliśmy się przyjrzeć, zostały zakopane głęboko w drzewie.

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

Użycie jest dość proste. Jeśli na przykład chcesz wiedzieć, czy napotkaliśmy plik

catch (Exception ex)
{
    var myExes = ex.innerExceptions<MyException>();
    if (myExes.Any(x => x.Message.StartsWith("Encountered my specific error")))
    {
        // ...
    }
}
Jeff Dege
źródło
O co chodzi Exception.GetBaseException()?
Kiquenet
1

Możesz użyć rekursji, aby utworzyć gdzieś metodę w klasie narzędziowej.

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

Posługiwać się:

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

Sugerowana metoda rozszerzenia (dobry pomysł @dtb)

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

Posługiwać się:

try
{
    // some code here
}
catch (Exception ex)
{
    Exception baseException = ex.GetFirstException();
}
Ćwiek
źródło
Przydatne public static Exception GetOriginalException(this Exception ex) { if (ex.InnerException == null) return ex; return ex.InnerException.GetOriginalException(); }
Kiquenet,
Nie dotyczy AggregateException.InnerExceptions?
Kiquenet
0

Możesz to zrobić także GetBaseException()dwukrotnie dzwoniąc :

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

To działa, ponieważ jeśli jest to AggregateException, pierwsze wywołanie przenosi cię do najbardziej wewnętrznego nie, AggregateExceptiona drugie wywołanie przenosi cię do najbardziej wewnętrznego wyjątku tego wyjątku. Jeśli pierwszy wyjątek nie jest wyjątkiem AggregateException, drugie wywołanie po prostu zwraca ten sam wyjątek.

JoeySchentrup
źródło
0

Wpadłem na to i chciałem móc wyświetlić listę wszystkich komunikatów o wyjątkach ze „stosu” wyjątków. Więc wymyśliłem to.

public static string GetExceptionMessages(Exception ex)
{
    if (ex.InnerException is null)
        return ex.Message;
    else return $"{ex.Message}\n{GetExceptionMessages(ex.InnerException)}";
}
Ken Lamb
źródło