Знайти внутрішній виняток без використання циклу 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.