Найти самое внутреннее исключение без использования цикла 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;
    }
}

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()?
— 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, то второй вызов просто возвращает то же исключение.


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.