Найти самое внутреннее исключение без использования цикла while?

84

Когда C # выдает исключение, оно может иметь внутреннее исключение. Я хочу получить самое внутреннее исключение или, другими словами, листовое исключение, которое не имеет внутреннего исключения. Я могу сделать это в цикле while:

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

Но мне было интересно, есть ли какой-нибудь однострочник, который я мог бы использовать вместо этого.

Дэниел Т.
источник
4
Я генерирую исключение в одном из моих классов, но мой класс используется библиотекой, которая принимает все исключения и генерирует свои собственные. Проблема в том, что исключение библиотеки является очень общим, и мне нужно знать, какое именно исключение я выбрал, чтобы знать, как справиться с проблемой. Что еще хуже, библиотека будет генерировать собственное исключение, многократно вложенное друг в друга, на произвольную глубину. Так например, бросит LibraryException -> LibraryException -> LibraryException -> MyException. Мое исключение всегда является последним в цепочке и не имеет собственного внутреннего исключения.
Дэниел Т.
О, я понимаю, почему вы можете перейти к самому внутреннему, сделав это сам (и варианты, такие как самое внутреннее, происходящее от определенного типа), но я не понимаю, в чем проблема с тем, что у вас уже есть.
Джон Ханна
О, я понимаю, что вы имеете в виду. Мне просто интересно, есть ли другой способ написать это. Я так привык LINQделать почти все, что мне кажется странным, когда мне приходится писать цикл. Я работаю в основном с доступом к данным, поэтому я работаю ICollectionsпрактически со всем, чем занимаюсь.
Дэниел Т.
@ Дэниел, это не LINQпросто маскировка того факта, что код по-прежнему должен зацикливаться? Есть ли в .NET настоящие команды на основе наборов?
Брэд,
6
Самый правильный ответ скрывается внизу с (в настоящее время) всего 2 голосами - Exception.GetBaseException (). Это было в структуре практически всегда. Спасибо batCattle за проверку работоспособности.
Джей

Ответы:

140

Один лайнер :)

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

Очевидно, что проще не сделать.

Как сказано в этом ответе Гленна МакЭлхо, это единственный надежный способ.

Драко Атер
источник
7
Метод GetBaseException () делает это без цикла. msdn.microsoft.com/en-us/library/…
codingoutloud,
8
Это работает, тогда как Exception.GetBaseException()для меня не работает.
Tarik
122

Я считаю, что Exception.GetBaseException()делает то же самое, что и эти решения.

Предостережение: из различных комментариев мы выяснили, что он не всегда буквально делает одно и то же, и в некоторых случаях рекурсивное / повторяющееся решение поможет вам продвинуться дальше. Это , как правило , сокровенное исключение, которое удручающе противоречива, благодаря определенным типам исключений , которые переопределяют значения по умолчанию. Однако, если вы поймаете определенные типы исключений и убедитесь, что они не чудаки (например, AggregateException), тогда я ожидаю, что оно получит законное самое внутреннее / самое раннее исключение.

Джош Саттерфилд
источник
3
+1 Нет ничего лучше, чем знать BCL, чтобы избежать запутанных решений. Ясно, что это наиболее правильный ответ.
Джей
4
По какой-то причине GetBaseException()не вернулось самое первое корневое исключение.
Tarik
4
Гленн МакЭлхо отметил, что GetBaseException () действительно не всегда выполняет то, что MSDN предлагает нам в целом (что «Исключение, являющееся основной причиной одного или нескольких последующих исключений»). В AggregateException он ограничен самым низким исключением того же типа, и, возможно, есть и другие.
Джош Саттерфилд 08
1
Не работает с моей проблемой. У меня на несколько уровней больше, чем это дает.
Giovanni B
2
GetBaseException()у меня не сработало, потому что самое верхнее исключение - это AggregateException.
Крис Балланс,
22

Цикл через InnerExceptions - единственный надежный способ.

Если перехваченное исключение является AggregateException, то GetBaseException()возвращает только самое внутреннее AggregateException.

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

Гленн МакЭлхо
источник
1
Хороший улов. Спасибо - это объясняет приведенные выше комментарии, в которых этот ответ не работал у некоторых людей. Это проясняет, что общее описание MSDN «основной причины одного или нескольких последующих исключений» не всегда соответствует вашему предположению, поскольку производные классы могут его переопределить. Настоящее правило, похоже, состоит в том, что все исключения в цепочке исключений «соглашаются» на GetBaseException () и возвращают один и тот же объект, что, по-видимому, имеет место для AggregateException.
Джош Саттерфилд 08
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;
    }
}
dtb
источник
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
armadillo.mx
источник
Спасибо за решение! Мало кто думает на 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()?
Kiquenet
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?
Kiquenet
0

Другой способ сделать это - GetBaseException()дважды позвонить :

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

Это работает, потому что, если это - AggregateException, первый вызов приведет вас к самому внутреннему не-, AggregateExceptionтогда второй вызов приведет вас к самому внутреннему исключению этого исключения. Если первое исключение не является исключением AggregateException, то второй вызов просто возвращает то же исключение.

JoeySchentrup
источник
0

Я столкнулся с этим и хотел иметь возможность перечислить все сообщения об исключениях из «стека» исключений. Итак, я придумал это.

public static string GetExceptionMessages(Exception ex)
{
    if (ex.InnerException is null)
        return ex.Message;
    else return $"{ex.Message}\n{GetExceptionMessages(ex.InnerException)}";
}
Кен Лэмб
источник