Знайти внутрішній виняток без використання циклу while?


84

Коли C # видає виняток, він може мати внутрішній виняток. Що я хочу зробити, це отримати внутрішній виняток, або іншими словами, виняток листя, який не має внутрішнього винятку. Я можу зробити це за деякий час:

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

Але мені було цікаво, чи є якийсь однокласник, за допомогою якого я міг би це зробити замість цього.


4
Я кидаю виняток в одному зі своїх класів, але моїм класом користується бібліотека, яка ковтає всі винятки та додає власні. Проблема в тому, що виняток з бібліотеки є дуже загальним, і мені потрібно знати, який конкретний виняток я кинув, щоб знати, як вирішити проблему. Що ще гірше, бібліотека викине власний виняток, кілька разів вкладений один в одного, на довільну глибину. Так, наприклад, це кине LibraryException -> LibraryException -> LibraryException -> MyException. Мій виняток завжди є останнім на ланцюжку і не має власного внутрішнього винятку.
Даніель Т.

О, я розумію, чому ви могли б перейти до найпотаємнішого, зробивши це сам (і варіанти, такі як найпотаємніший, що походить від певного типу), але я не розумію, в чому проблема в тому, що у вас вже є.
Джон Ханна,

О, я розумію, що ти маєш на увазі. Мені просто було цікаво, чи є інший спосіб написати це. Я настільки звик використовувати LINQмайже все, що здається дивним, коли мені доводиться писати цикл. Я працюю в основному з доступом до даних, тому я працюю ICollectionsмайже з усім, що роблю.
Даніель Т.

@Daniel, чи не LINQпросто маскує той факт, що код все-таки по суті повинен циклічно повторювати ? Чи є в .NET справжні команди на основі набору?
Бред

6
Найбільш правильна відповідь криється внизу, маючи (на даний момент) лише 2 голоси - Exception.GetBaseException (). Це було в рамках в основному назавжди. Дякуємо batCattle за перевірку стану здоров'я.
Джей,

Відповіді:


140

Oneliner :)

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

Очевидно, ви не можете зробити це простішим.

Як зазначив у цій відповіді Гленн Макелхоу, це єдиний надійний спосіб.


7
Метод GetBaseException () робить це без циклу. msdn.microsoft.com/en-us/library/…
codingoutloud

8
Це працює, тоді як Exception.GetBaseException()для мене ні.
Тарік

122

Я вважаю, Exception.GetBaseException()робить те саме, що і ці рішення.

Зауваження: З різних коментарів ми з’ясували, що це не завжди буквально робить одне і те ж, і в деяких випадках рекурсивне / ітераційне рішення приведе вас далі. Це , як правило , сокровенне виняток, яке гнітюче суперечлива, завдяки певним типам винятків , які скасовують значення за замовчуванням. Однак, якщо ви ловите конкретні типи винятків і впевнені, що вони не є диваками (як, наприклад, AggregateException), тоді я сподівався б, що він отримає законний внутрішній / ранній виняток.


3
+1 Немає нічого подібного до знання BCL, щоб уникнути заплутаних рішень. Очевидно, це найбільш правильна відповідь.
Джей

4
З якоїсь причини GetBaseException()не повернув найперший виняток кореня.
Тарік

4
Гленн Мак-Елхоу зазначив, що справді GetBaseException () не завжди робить те, що MSDN припускає, що ми можемо очікувати загалом (що "виняток є основною причиною одного або декількох наступних винятків"). У AggregateException це обмежується найменшим винятком того самого типу, і, можливо, є й інші.
Джош Саттерфілд

1
Не працює для мого випуску. У мене на кілька рівнів нижче, ніж те, що це забезпечує.
Джованні Б

2
GetBaseException()у мене не спрацював, оскільки найвищим винятком є ​​AggregateException.
Chris Ballance

22

Перегляд InnerExceptions - єдиний надійний спосіб.

Якщо спійманим винятком є ​​AggregateException, тоді GetBaseException()повертається лише внутрішній AggregateException.

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


1
Хороший улов. Дякую - це пояснює коментарі вище, де ця відповідь не працювала для деяких людей. Це дає зрозуміти, що загальний опис MSDN "першопричини одного або кількох наступних винятків" не завжди є тим, що ви вважаєте, оскільки похідні класи можуть його замінити. Справжнім правилом, схоже, є те, що всі винятки в ланцюжку винятків "погоджуються" щодо GetBaseException () і повертають той самий об'єкт, що, схоже, має місце для AggregateException.
Джош Саттерфілд

12

Якщо ви не знаєте, наскільки глибоко вкладені внутрішні винятки, неможливо обійти цикл або рекурсію.

Звичайно, ви можете визначити метод розширення, який абстрагує це:

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

10

Я знаю, що це стара публікація, але я здивований, що ніхто не запропонував, GetBaseException()який метод у Exceptionкласі:

catch (Exception x)
{
    var baseException = x.GetBaseException();
}

Це існує з .NET 1.1. Документація тут: http://msdn.microsoft.com/en-us/library/system.exception.getbaseexception(v=vs.71).aspx


Дійсно було вказано раніше, 5 грудня 2012 р.
armadillo.mx

2

Іноді у вас може бути багато внутрішніх винятків (багато винятків з бульбашками). У цьому випадку ви можете зробити:

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

1

Не зовсім один рядок, але близько:

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

1

Насправді це так просто, що ти міг би використати Exception.GetBaseException()

Try
      //Your code
Catch ex As Exception
      MessageBox.Show(ex.GetBaseException().Message, My.Settings.MsgBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
End Try

Дякуємо за ваше рішення! У VB мало хто думає: D
Федеріко Наваррете

1
І є причина чому! : P
Йода

1

Вам доведеться робити цикл, і, якщо цикл буде, це зручніше перенести цикл в окрему функцію.

Я створив метод розширення для боротьби з цим. Він повертає список усіх внутрішніх винятків зазначеного типу, переслідуючи Exception.InnerException та AggregateException.InnerExceptions.

У моїй конкретній проблемі переслідування внутрішніх винятків було складнішим, ніж зазвичай, оскільки винятки створювали конструктори класів, які викликались через рефлексію. Виняток, який ми ловили, мав InnerException типу TargetInvocationException, і винятки, які нам насправді потрібно було розглянути, були поховані глибоко в дереві.

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

Використання досить просте. Якщо, наприклад, ви хочете знати, чи не стикалися ми з

catch (Exception ex)
{
    var myExes = ex.innerExceptions<MyException>();
    if (myExes.Any(x => x.Message.StartsWith("Encountered my specific error")))
    {
        // ...
    }
}

Про що Exception.GetBaseException()?
Кікенет

1

Ви можете використовувати рекурсію, щоб десь створити метод у класі корисності.

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

Використання:

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

Запропонований метод розширення (гарна ідея @dtb)

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

Використання:

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

Корисно public static Exception GetOriginalException(this Exception ex) { if (ex.InnerException == null) return ex; return ex.InnerException.GetOriginalException(); }
Kiquenet

Не застосовується AggregateException.InnerExceptions?
Кікенет

0

Ще один спосіб зробити це - зателефонувати GetBaseException()двічі:

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

Це працює, тому що якщо це так AggregateException, перший виклик приведе вас до внутрішнього не-, AggregateExceptionтоді як другий виклик - до внутрішнього винятку цього винятку. Якщо перший виняток не є AggregateException, то другий виклик просто повертає той самий виняток.


0

Я зіткнувся з цим і хотів мати можливість перерахувати всі повідомлення про винятки із "стека" винятків. Отже, я це придумав.

public static string GetExceptionMessages(Exception ex)
{
    if (ex.InnerException is null)
        return ex.Message;
    else return $"{ex.Message}\n{GetExceptionMessages(ex.InnerException)}";
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.