Mendapatkan semua pesan dari InnerException (s)?


92

Apakah ada cara untuk menulis kode "tangan pendek" gaya LINQ untuk berjalan ke semua tingkat InnerException (s) of Exception yang dilempar? Saya lebih suka menulisnya di tempat daripada memanggil fungsi ekstensi (seperti di bawah) atau mewarisi Exceptionkelas.

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
Bolehkah saya bertanya mengapa Anda ingin menggunakan sesuatu yang lain selain metode Ekstensi? Kode Anda terlihat bagus bagi saya, dan dapat digunakan kembali di mana pun dalam kode Anda.
ken2k

@ ken2k: Meskipun Anda tidak ingin membuat pesan seperti dia sekarang ...
Jeff Mercado

1
@JeffMercado Ya, tapi apa masalah dengan konsep "metode ekstensi"?
ken2k

@ ken2k: Sejujurnya, saya tidak begitu mengerti pertanyaan Anda ... Anda baru saja menyebutkan bahwa kode "terlihat bagus" ketika ada cacat.
Jeff Mercado

1
Berhati-hatilah AggregateExceptionkarena berperilaku sedikit berbeda. Anda harus berjalan melalui InnerExceptionsproperti sebagai gantinya. Menyediakan metode ekstensi praktis di sini: stackoverflow.com/a/52042708/661933 untuk mencakup kedua kasus.
nawfal

Jawaban:


92

Sayangnya LINQ tidak menawarkan metode yang dapat memproses struktur hierarki, hanya koleksi.

Saya sebenarnya memiliki beberapa metode ekstensi yang dapat membantu melakukan ini. Saya tidak memiliki kode pasti di tangan tetapi mereka seperti ini:

// 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);
}

Kemudian dalam hal ini Anda dapat melakukan ini untuk menghitung melalui pengecualian:

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

Maksudmu seperti ini?

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);
    }
}

Dengan cara ini Anda bisa LINQ di seluruh hierarki pengecualian Anda, seperti ini:

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

2
Jauh lebih bersih daripada solusi yang diusulkan
Beras

1
@ Beras harap dicatat bahwa solusi yang diusulkan adalah generalisasi dari masalah ini untuk beberapa skenario perataan. Fakta bahwa itu lebih kompleks diharapkan.
julealgon

31

Bagaimana dengan kode ini:

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;
}

Pemakaian:

Console.WriteLine(e.GetExceptionMessages())

Contoh keluaran:

Tidak ada endpoint yang mendengarkan di http: //nnn.mmm.kkk.ppp: 8000 / routingservice / router yang dapat menerima pesan tersebut. Ini sering disebabkan oleh alamat yang salah atau tindakan SOAP. Lihat InnerException, jika ada, untuk detail selengkapnya.

InnerException: Tidak dapat terhubung ke server jarak jauh

InnerException: Tidak ada koneksi yang dapat dibuat karena mesin target secara aktif menolaknya 127.0.0.1:8000


3
Anda harus benar-benar mempertimbangkan untuk menggunakan di StringBuildersini. Juga metode ekstensi IMO harus dibuang NullReferenceExceptionsaat dipanggil pada referensi null.
dstarkowski

27

Saya tahu ini jelas, tetapi mungkin tidak untuk semua.

exc.ToString();

Ini akan melalui semua pengecualian batin Anda dan mengembalikan semua pesan, tetapi bersama dengan jejak tumpukan dll.


3
Tidak apa-apa jika Anda senang hidup dengan semua jejak tumpukan penuh yang dicampur dengan ToString. Itu sering tidak sesuai dengan konteksnya misalnya jika pesan ditujukan ke pengguna. Di sisi lain Message TIDAK memberikan pesan pengecualian dalam (tidak seperti ToString yang berulang). Apa yang paling sering kita inginkan adalah FullMessage yang tidak ada yang merupakan pesan dari orang tua dan pengecualian batin.
Ricibob

16

Anda tidak memerlukan metode ekstensi atau panggilan rekursif:

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);
}

Cemerlang! Seandainya aku memikirkannya.
Raul Marquez

11

LINQ umumnya digunakan untuk bekerja dengan kumpulan objek. Namun, bisa dibilang, dalam kasus Anda tidak ada kumpulan objek (kecuali grafik). Jadi meskipun beberapa kode LINQ dimungkinkan, IMHO akan agak berbelit-belit atau artifisial.

Di sisi lain, contoh Anda terlihat seperti contoh utama di mana metode penyuluhan sebenarnya masuk akal. Belum lagi masalah seperti penggunaan kembali, enkapsulasi, dll.

Saya akan tetap menggunakan metode ekstensi, meskipun saya mungkin telah menerapkannya seperti itu:

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();
}

Tapi itu sebagian besar masalah selera.


7

Saya tidak berpikir demikian, pengecualian bukanlah IEnumerable sehingga Anda tidak dapat melakukan kueri LINQ terhadapnya sendiri.

Metode ekstensi untuk mengembalikan pengecualian dalam akan bekerja seperti ini

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

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

Anda kemudian dapat menambahkan semua pesan menggunakan query linq seperti ini:

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

6

Untuk menambahkan ke orang lain, Anda mungkin ingin mengizinkan pengguna memutuskan cara memisahkan pesan:

    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

Saya hanya akan meninggalkan versi paling ringkas di sini:

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

Sebagian besar solusi yang disajikan di sini memiliki kesalahan implementasi berikut:

  • menangani nullpengecualian
  • menangani pengecualian bagian dalam dari AggregateException
  • tentukan kedalaman maksimum untuk pengecualian dalam berulang (yaitu dengan ketergantungan melingkar)

Implementasi yang lebih baik adalah di sini:

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;
}

Contoh penggunaan:

try
{
    // ...
}
catch(Exception e)
{
    Log.Error(e, e.AggregateMessages());
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.