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