Nhận tất cả thông báo từ (các) InnerException?


92

Có cách nào để viết mã kiểu LINQ "tay ngắn" để đi đến tất cả các cấp của (các) bên trong của Ngoại lệ được ném ra không? Tôi muốn viết nó tại chỗ thay vì gọi một hàm mở rộng (như bên dưới) hoặc kế thừa Exceptionlớp.

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
Tôi có thể hỏi bạn tại sao bạn muốn sử dụng một cái gì đó khác ngoài các phương pháp Mở rộng không? Mã của bạn trông ổn đối với tôi và có thể sử dụng lại ở mọi nơi trong mã của bạn.
— ken2k

@ ken2k: Mặc dù bạn sẽ không muốn để xây dựng các thông điệp cách anh có nó ngay bây giờ ...
— Jeff Mercado

1
@JeffMercado Có, nhưng vấn đề với khái niệm "phương thức mở rộng" là gì?
— ken2k

@ ken2k: Thành thật mà nói, tôi không thực sự hiểu câu hỏi của bạn ... bạn chỉ đề cập rằng mã "trông ổn" khi nó có sai sót.
— Jeff Mercado

1
Chỉ cần cẩn thận trong tâm trí AggregateException, hành vi sẽ khác một chút. InnerExceptionsThay vào đó, bạn sẽ phải đi bộ qua tài sản. Đã cung cấp một phương thức mở rộng tiện dụng tại đây: stackoverflow.com/a/52042708/661933 để bao gồm cả hai trường hợp.
— nawfal

Câu trả lời:


92

Thật không may, LINQ không cung cấp các phương thức có thể xử lý cấu trúc phân cấp, chỉ các bộ sưu tập.

Tôi thực sự có một số phương pháp mở rộng có thể giúp thực hiện việc này. Tôi không có mã chính xác trong tay nhưng chúng giống như sau:

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

Sau đó, trong trường hợp này, bạn có thể làm điều này để liệt kê các trường hợp ngoại lệ:

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

Ý bạn là như thế này?

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

Bằng cách này, bạn có thể LINQ trên toàn bộ hệ thống phân cấp ngoại lệ của mình, như sau:

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

2
Sạch hơn nhiều so với giải pháp được đề xuất
— Rice

1
@Rice xin lưu ý rằng giải pháp được đề xuất là tổng quát của vấn đề này cho nhiều tình huống làm phẳng. Thực tế là nó phức tạp hơn được mong đợi.
— julealgon

31

Làm thế nào về mã này:

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

Sử dụng:

Console.WriteLine(e.GetExceptionMessages())

Ví dụ về đầu ra:

Không có điểm cuối nào đang nghe tại http: //nnn.mmm.kkk.ppp: 8000 / routingservice / bộ định tuyến có thể chấp nhận thông báo. Điều này thường do địa chỉ không chính xác hoặc hành động SOAP gây ra. Xem InnerException, nếu có, để biết thêm chi tiết.

InnerException: Không thể kết nối với máy chủ từ xa

InnerException: Không thể thực hiện kết nối vì máy mục tiêu đã chủ động từ chối nó 127.0.0.1:8000


3
Bạn thực sự nên xem xét sử dụng StringBuilderở đây. Ngoài ra, phương thức mở rộng IMO nên ném NullReferenceExceptionkhi được gọi trên tham chiếu rỗng.
— dstarkowski

27

Đối với những người đang chờ đợi một lớp lót.

exc.ToString();

Điều này sẽ đi qua tất cả các ngoại lệ bên trong của bạn và trả về tất cả các thư, nhược điểm là nó cũng sẽ chứa dấu vết ngăn xếp, v.v.


3
Thật tốt nếu bạn hạnh phúc khi sống với tất cả các dấu vết ngăn xếp đầy đủ bị lừa dối với ToString. Điều đó thường không phù hợp với ngữ cảnh, ví dụ như nếu tin nhắn được gửi đến người dùng. Mặt khác, Thông báo KHÔNG cung cấp Thông báo ngoại lệ bên trong (không giống như ToString không đệ quy). Những gì chúng ta thường muốn nhất là FullMessage không tồn tại, tất cả đều là thông báo từ các ngoại lệ gốc và nội bộ.
— Ricibob

16

Bạn không cần các phương thức mở rộng hoặc cuộc gọi đệ quy:

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

Xuất sắc! Ước gì tôi nghĩ về nó.
— Raul Marquez

11

LINQ thường được sử dụng để làm việc với các tập hợp các đối tượng. Tuy nhiên, có thể nói, trong trường hợp của bạn không có tập hợp các đối tượng (mà là một biểu đồ). Vì vậy, mặc dù một số mã LINQ có thể có, nhưng IMHO thì nó sẽ là phức tạp hoặc giả tạo.

Mặt khác, ví dụ của bạn trông giống như một ví dụ điển hình trong đó các phương pháp mở rộng thực sự hợp lý. Không nói về các vấn đề như tái sử dụng, đóng gói, v.v.

Tôi sẽ ở lại với một phương pháp mở rộng, mặc dù tôi có thể đã triển khai nó theo cách đó:

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

Nhưng đó chủ yếu là vấn đề về thị hiếu.


7

Tôi không nghĩ vậy, ngoại lệ không phải là IEnumerable nên bạn không thể tự thực hiện truy vấn linq đối với truy vấn của nó.

Một phương thức mở rộng để trả về các ngoại lệ bên trong sẽ hoạt động như thế này

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

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

sau đó bạn có thể nối tất cả các thư bằng cách sử dụng truy vấn linq như sau:

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

6

Để thêm vào những người khác, bạn có thể muốn để người dùng quyết định cách tách các thư:

    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

Tôi chỉ để lại phiên bản ngắn gọn nhất ở đây:

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

Hầu hết các giải pháp được viết sẵn ở đây đều có các lỗi triển khai sau:

  • xử lý các nulltrường hợp ngoại lệ
  • xử lý các ngoại lệ bên trong của AggregateException
  • xác định độ sâu tối đa cho các ngoại lệ bên trong đệ quy (tức là với các phụ thuộc vòng)

Cách triển khai tốt hơn là đây:

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

Ví dụ sử dụng:

try
{
    // ...
}
catch(Exception e)
{
    Log.Error(e, e.AggregateMessages());
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookie và Chính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.