รับข้อความทั้งหมดจาก InnerException หรือไม่


92

มีวิธีใดในการเขียนโค้ด "มือสั้น" สไตล์ LINQ สำหรับการเดินไปยังทุกระดับของ InnerException (s) ของ Exception ที่ถูกโยน? ฉันอยากจะเขียนแทนการเรียกฟังก์ชันส่วนขยาย (ตามด้านล่าง) หรือสืบทอดExceptionคลาส

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
ฉันขอถามคุณได้ไหมว่าทำไมคุณถึงต้องการใช้อย่างอื่นนอกเหนือจากวิธีการขยาย รหัสของคุณดูดีสำหรับฉันและสามารถใช้ซ้ำได้ทุกที่ในรหัสของคุณ
ken2k

@ ken2k: แม้ว่าคุณจะไม่ต้องการสร้างข้อความในแบบที่เขามีในตอนนี้ ...
Jeff Mercado

1
@JeffMercado ใช่ แต่ปัญหาเกี่ยวกับแนวคิดของ "วิธีการขยาย" คืออะไร?
ken2k

@ ken2k: พูดตามตรงฉันไม่เข้าใจคำถามของคุณจริงๆ ... คุณเพิ่งพูดว่ารหัส "ดูดี" เมื่อมีข้อบกพร่อง
Jeff Mercado

1
เพียงแค่ระวังAggregateExceptionพฤติกรรมที่แตกต่างกันเล็กน้อย คุณจะต้องเดินผ่านInnerExceptionsทรัพย์สินแทน มีวิธีการขยายที่สะดวกที่นี่: stackoverflow.com/a/52042708/661933เพื่อให้ครอบคลุมทั้งสองกรณี
nawfal

คำตอบ:


92

น่าเสียดายที่ LINQ ไม่ได้นำเสนอวิธีการที่สามารถประมวลผลโครงสร้างลำดับชั้นได้เฉพาะคอลเล็กชันเท่านั้น

ฉันมีวิธีการขยายบางอย่างที่สามารถช่วยได้ ฉันไม่มีรหัสที่แน่นอนอยู่ในมือ แต่เป็นแบบนี้:

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

จากนั้นในกรณีนี้คุณสามารถทำได้เพื่อระบุข้อยกเว้น:

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

คุณหมายถึงอะไรแบบนี้?

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

ด้วยวิธีนี้คุณสามารถ LINQ ในลำดับชั้นข้อยกเว้นทั้งหมดของคุณได้ดังนี้:

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

2
สะอาดกว่าวิธีแก้ปัญหาที่เสนอมาก
ข้าว

1
@Rice โปรดทราบว่าวิธีแก้ปัญหาที่นำเสนอเป็นลักษณะทั่วไปของปัญหานี้สำหรับสถานการณ์การแบนหลาย คาดว่าจะมีความซับซ้อนมากขึ้น
julealgon

31

รหัสนี้เป็นอย่างไร:

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

การใช้งาน:

Console.WriteLine(e.GetExceptionMessages())

ตัวอย่างผลลัพธ์:

ไม่มีการฟังปลายทางที่http: //nnn.mmm.kkk.ppp: 8000 / Routingservice / routerที่สามารถรับข้อความได้ ซึ่งมักเกิดจากที่อยู่ที่ไม่ถูกต้องหรือการทำงานของ SOAP ดู InnerException หากมีสำหรับรายละเอียดเพิ่มเติม

InnerException: ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ระยะไกล

InnerException: ไม่สามารถทำการเชื่อมต่อได้เนื่องจากเครื่องเป้าหมายปฏิเสธอย่างแข็งขัน 127.0.0.1:8000


3
คุณควรพิจารณาใช้StringBuilderที่นี่จริงๆ นอกจากนี้วิธีการขยาย IMO ควรโยนNullReferenceExceptionเมื่อเรียกใช้การอ้างอิงที่ว่าง
dstarkowski

27

ฉันรู้ว่าสิ่งนี้ชัดเจน แต่อาจไม่ใช่ทั้งหมด

exc.ToString();

สิ่งนี้จะผ่านข้อยกเว้นภายในทั้งหมดของคุณและส่งคืนข้อความทั้งหมด แต่จะรวมกับการติดตามสแต็กเป็นต้น


3
ใช่ว่าจะดีถ้าคุณมีความสุขที่จะอยู่กับการติดตามสแต็กเต็มรูปแบบทั้งหมดที่ได้รับ ToString ซึ่งมักไม่เหมาะกับบริบทเช่นหากข้อความส่งถึงผู้ใช้ ในทางกลับกันข้อความไม่ให้ข้อความข้อยกเว้นภายใน (ไม่เหมือนกับ ToString ที่เรียกคืน) สิ่งที่เราต้องการมากที่สุดคือ FullMessage ที่ไม่มีอยู่ซึ่งเป็นข้อความทั้งหมดจากข้อยกเว้นหลักและข้อยกเว้นภายใน
Ricibob

16

คุณไม่จำเป็นต้องใช้วิธีการขยายหรือการโทรซ้ำ:

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

ยอดเยี่ยม! หวังว่าฉันจะคิดเกี่ยวกับมัน
Raul Marquez

11

โดยทั่วไป LINQ จะใช้เพื่อทำงานกับคอลเลกชันของวัตถุ อย่างไรก็ตามเนื้อหาในกรณีของคุณไม่มีการรวบรวมวัตถุ (แต่เป็นกราฟ) ดังนั้นแม้ว่ารหัส LINQ บางรหัสอาจเป็นไปได้ แต่ IMHO ก็ค่อนข้างซับซ้อนหรือเป็นของเทียม

ในทางกลับกันตัวอย่างของคุณดูเหมือนเป็นตัวอย่างที่สำคัญซึ่งวิธีการขยายมีความสมเหตุสมผล ไม่พูดถึงประเด็นต่างๆเช่นการใช้ซ้ำการห่อหุ้ม ฯลฯ

ฉันจะใช้วิธีการขยายแม้ว่าฉันอาจจะใช้วิธีนั้น:

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

แต่นั่นเป็นปัญหาส่วนใหญ่ของรสชาติ


7

ฉันไม่คิดอย่างนั้นข้อยกเว้นไม่ใช่ IEnumerable ดังนั้นคุณจึงไม่สามารถดำเนินการค้นหา linq เทียบกับหนึ่งได้ด้วยตัวเอง

วิธีการขยายเพื่อส่งคืนข้อยกเว้นภายในจะได้ผลเช่นนี้

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

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

จากนั้นคุณสามารถต่อท้ายข้อความทั้งหมดโดยใช้แบบสอบถาม linq ดังนี้:

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

6

หากต้องการเพิ่มให้กับผู้อื่นคุณอาจต้องการให้ผู้ใช้ตัดสินใจว่าจะแยกข้อความอย่างไร:

    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

ฉันจะทิ้งเวอร์ชันที่กระชับที่สุดไว้ที่นี่:

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

โซลูชันส่วนใหญ่ที่นำเสนอไว้ที่นี่มีข้อผิดพลาดในการใช้งานต่อไปนี้:

  • จัดการnullข้อยกเว้น
  • จัดการข้อยกเว้นภายในของ AggregateException
  • กำหนดความลึกสูงสุดสำหรับการเรียกคืนข้อยกเว้นภายใน (เช่นด้วยการอ้างอิงแบบวงกลม)

การใช้งานที่ดีขึ้นอยู่ที่นี่:

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

ตัวอย่างการใช้งาน:

try
{
    // ...
}
catch(Exception e)
{
    Log.Error(e, e.AggregateMessages());
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.