InnerException (lar) dan tüm mesajları alıyor musunuz?


92

Atılan İstisnanın tüm InnerException (ları) seviyelerine yürümek için LINQ tarzı "kısa el" kodu yazmanın bir yolu var mı? Bir uzantı işlevi çağırmak (aşağıdaki gibi) veya Exceptionsınıfı miras almak yerine yerinde yazmayı tercih ederim .

static class Extensions
{
    public static string GetaAllMessages(this Exception exp)
    {
        string message = string.Empty;
        Exception innerException = exp;

        do
        {
            message = message + (string.IsNullOrEmpty(innerException.Message) ? string.Empty : innerException.Message);
            innerException = innerException.InnerException;
        }
        while (innerException != null);

        return message;
    }
}; 

2
Neden Uzatma yöntemlerinden başka bir şey kullanmak istediğinizi sorabilir miyim? Kodunuz bana iyi görünüyor ve kodunuzun her yerinde yeniden kullanılabilir.
ken2k

@ ken2k: Mesajları şu anda sahip olduğu şekilde oluşturmak istemeseniz de ...
Jeff Mercado

1
@JeffMercado Evet, ama "uzantı yöntemi" kavramıyla ilgili sorun nedir?
ken2k

@ ken2k: Dürüst olmak gerekirse, sorunuzu gerçekten anlamıyorum ... kodun kusurlu olduğunda "iyi göründüğünden" bahsettiniz.
Jeff Mercado

1
Sadece AggregateExceptionbiraz farklı davrandığını unutmayın . Bunun InnerExceptionsyerine mülkten geçmeniz gerekecek . Burada kullanışlı bir uzatma yöntemi sağlanmıştır: her iki durumu da kapsayacak şekilde stackoverflow.com/a/52042708/661933 .
nawfal

Yanıtlar:


92

Ne yazık ki LINQ, hiyerarşik yapıları işleyebilecek yöntemler sunmuyor, yalnızca koleksiyonları.

Aslında bunu yapmanıza yardımcı olabilecek bazı uzatma yöntemlerine sahibim. Elimde tam kod yok ama bunlar şuna benzer:

// all error checking left out for brevity

// a.k.a., linked list style enumerator
public static IEnumerable<TSource> FromHierarchy<TSource>(
    this TSource source,
    Func<TSource, TSource> nextItem,
    Func<TSource, bool> canContinue)
{
    for (var current = source; canContinue(current); current = nextItem(current))
    {
        yield return current;
    }
}

public static IEnumerable<TSource> FromHierarchy<TSource>(
    this TSource source,
    Func<TSource, TSource> nextItem)
    where TSource : class
{
    return FromHierarchy(source, nextItem, s => s != null);
}

Sonra bu durumda, istisnalar üzerinden numaralandırmak için bunu yapabilirsiniz:

public static string GetaAllMessages(this Exception exception)
{
    var messages = exception.FromHierarchy(ex => ex.InnerException)
        .Select(ex => ex.Message);
    return String.Join(Environment.NewLine, messages);
}

81

Bunun gibi bir şey mi ifade etmeye çalışıyorsun?

public static class Extensions
{
    public static IEnumerable<Exception> GetInnerExceptions(this Exception ex)
    {
        if (ex == null)
        {
            throw new ArgumentNullException("ex");
        }

        var innerException = ex;
        do
        {
            yield return innerException;
            innerException = innerException.InnerException;
        }
        while (innerException != null);
    }
}

Bu şekilde, tüm istisna hiyerarşiniz üzerinde LINQ yapabilirsiniz, örneğin:

exception.GetInnerExceptions().Where(e => e.Message == "Oops!");

2
Önerilen çözümden çok daha temiz
Rice

1
@Rice, önerilen çözümün, birden fazla düzleştirme senaryosu için bu sorunun bir genellemesi olduğunu lütfen unutmayın. Daha karmaşık olması bekleniyor.
julealgon

31

Bu koda ne dersiniz:

private static string GetExceptionMessages(this Exception e, string msgs = "")
{
  if (e == null) return string.Empty;
  if (msgs == "") msgs = e.Message;
  if (e.InnerException != null)
    msgs += "\r\nInnerException: " + GetExceptionMessages(e.InnerException);
  return msgs;
}

Kullanım:

Console.WriteLine(e.GetExceptionMessages())

Çıktı örneği:

Http: //nnn.mmm.kkk.ppp: 8000 / routingservice / router'da mesajı kabul edebilecek hiçbir uç nokta dinleme yoktu . Bu genellikle yanlış adres veya SOAP eyleminden kaynaklanır. Daha fazla ayrıntı için, varsa InnerException'a bakın.

InnerException: Uzak sunucuya bağlanılamıyor

InnerException: Hedef makine aktif olarak reddettiği için bağlantı kurulamadı 127.0.0.1:8000


3
StringBuilderBurada kullanmayı gerçekten düşünmelisiniz . Ayrıca IMO uzantı yöntemi NullReferenceExceptionboş referans üzerinde çağrıldığında atmalıdır.
dstarkowski

27

Bunun açık olduğunu biliyorum, ama belki de hepsi için değil.

exc.ToString();

Bu, tüm iç istisnalarınızdan geçecek ve tüm mesajları döndürür, ancak yığın izleme vb. İle birlikte.


3
ToString ile bozulan tüm yığın iziyle yaşamaktan mutluysanız, bu sorun değil. Bu genellikle bağlama uymaz, örneğin mesaj bir kullanıcıya gidiyorsa. Öte yandan, Mesaj iç istisna Mesajını VERMEZ (tekrar eden ToString'in aksine). En sık istediğimiz şey, tümü ebeveynden ve iç istisnalardan gelen mesaj olan varolmayan FullMessage'dır.
Ricibob

16

Uzatma yöntemlerine veya özyinelemeli çağrılara ihtiyacınız yok:

try {
  // Code that throws exception
}
catch (Exception e)
{
  var messages = new List<string>();
  do
  {
    messages.Add(e.Message);
    e = e.InnerException;
  }
  while (e != null) ;
  var message = string.Join(" - ", messages);
}

Parlak! Keşke bunu düşünseydim.
Raul Marquez

11

LINQ, genellikle nesne koleksiyonlarıyla çalışmak için kullanılır. Bununla birlikte, tartışmasız, sizin durumunuzda nesne koleksiyonu yoktur (ancak bir grafik). Dolayısıyla, bazı LINQ kodları mümkün olsa bile, IMHO oldukça kıvrımlı veya yapay olacaktır.

Öte yandan, örneğiniz, uzatma yöntemlerinin gerçekten makul olduğu birinci sınıf bir örnek gibi görünüyor. Yeniden kullanım, kapsülleme vb. Konulardan bahsetmiyorum bile.

Bu şekilde uygulamış olsam da, bir uzatma yöntemiyle kalırdım:

public static string GetAllMessages(this Exception ex)
{
   if (ex == null)
     throw new ArgumentNullException("ex");

   StringBuilder sb = new StringBuilder();

   while (ex != null)
   {
      if (!string.IsNullOrEmpty(ex.Message))
      {
         if (sb.Length > 0)
           sb.Append(" ");

         sb.Append(ex.Message);
      }

      ex = ex.InnerException;
   }

   return sb.ToString();
}

Ancak bu büyük ölçüde bir zevk meselesidir.


7

Ben öyle düşünmüyorum, istisna bir IEnumerable değildir, bu yüzden kendi başına bir linq sorgusu yapamazsınız.

İç istisnaları döndürmek için bir uzatma yöntemi şu şekilde çalışır

public static class ExceptionExtensions
{
    public static IEnumerable<Exception> InnerExceptions(this Exception exception)
    {
        Exception ex = exception;

        while (ex != null)
        {
            yield return ex;
            ex = ex.InnerException;
        }
    }
}

daha sonra aşağıdaki gibi bir linq sorgusu kullanarak tüm mesajları ekleyebilirsiniz:

var allMessageText = string.Concat(exception.InnerExceptions().Select(e => e.Message + ","));

6

Başkalarına eklemek için, kullanıcının mesajları nasıl ayıracağına karar vermesine izin verebilirsiniz:

    public static string GetAllMessages(this Exception ex, string separator = "\r\nInnerException: ")
    {
        if (ex.InnerException == null)
            return ex.Message;

        return ex.Message + separator + GetAllMessages(ex.InnerException, separator);
    }

6
    public static string GetExceptionMessage(Exception ex)
    {
        if (ex.InnerException == null)
        {
            return string.Concat(ex.Message, System.Environment.NewLine, ex.StackTrace);
        }
        else
        {
            // Retira a última mensagem da pilha que já foi retornada na recursividade anterior
            // (senão a última exceção - que não tem InnerException - vai cair no último else, retornando a mesma mensagem já retornada na passagem anterior)
            if (ex.InnerException.InnerException == null)
                return ex.InnerException.Message;
            else
                return string.Concat(string.Concat(ex.InnerException.Message, System.Environment.NewLine, ex.StackTrace), System.Environment.NewLine, GetExceptionMessage(ex.InnerException));
        }
    }

4

Sadece en kısa versiyonu burada bırakacağım:

public static class ExceptionExtensions
{
    public static string GetMessageWithInner(this Exception ex) =>
        string.Join($";{ Environment.NewLine }caused by: ",
            GetInnerExceptions(ex).Select(e => $"'{ e.Message }'"));

    public static IEnumerable<Exception> GetInnerExceptions(this Exception ex)
    {
        while (ex != null)
        {
            yield return ex;
            ex = ex.InnerException;
        }
    }
}

3
public static class ExceptionExtensions
{
    public static IEnumerable<Exception> GetAllExceptions(this Exception ex)
    {
        Exception currentEx = ex;
        yield return currentEx;
        while (currentEx.InnerException != null)
        {
            currentEx = currentEx.InnerException;
            yield return currentEx;
        }
    }

    public static IEnumerable<string> GetAllExceptionAsString(this Exception ex)
    {            
        Exception currentEx = ex;
        yield return currentEx.ToString();
        while (currentEx.InnerException != null)
        {
            currentEx = currentEx.InnerException;
            yield return currentEx.ToString();
        }            
    }

    public static IEnumerable<string> GetAllExceptionMessages(this Exception ex)
    {
        Exception currentEx = ex;
        yield return currentEx.Message;
        while (currentEx.InnerException != null)
        {
            currentEx = currentEx.InnerException;
            yield return currentEx.Message;
        }
    }
}

1

Burada sunulan çözümlerin çoğunda aşağıdaki uygulama hataları bulunur:

  • nullistisnaları ele almak
  • iç istisnaları ele almak AggregateException
  • Yinelenen iç istisnalar için maksimum derinlik tanımlayın (yani döngüsel bağımlılıklar ile)

Daha iyi bir uygulama şudur:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public static string AggregateMessages(this Exception ex) =>
    ex.GetInnerExceptions()
        .Aggregate(
            new StringBuilder(),
            (sb, e) => sb.AppendLine(e.Message),
            sb => sb.ToString());

public static IEnumerable<Exception> GetInnerExceptions(this Exception ex, int maxDepth = 5)
{
    if (ex == null || maxDepth <= 0)
    {
        yield break;
    }

    yield return ex;

    if (ex is AggregateException ax)
    {
        foreach(var i in ax.InnerExceptions.SelectMany(ie => GetInnerExceptions(ie, maxDepth - 1)))
            yield return i;
    }

    foreach (var i in GetInnerExceptions(ex.InnerException, maxDepth - 1))
        yield return i;
}

Örnek kullanım:

try
{
    // ...
}
catch(Exception e)
{
    Log.Error(e, e.AggregateMessages());
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.