Tìm ngoại lệ bên trong nhất mà không sử dụng vòng lặp while?


82

Khi C # ném một ngoại lệ, nó có thể có một ngoại lệ bên trong. Điều tôi muốn làm là có được ngoại lệ bên trong nhất, hay nói cách khác, ngoại lệ của lá không có ngoại lệ bên trong. Tôi có thể làm điều này trong vòng lặp while:

while (e.InnerException != null)
{
    e = e.InnerException;
}

Nhưng tôi đã tự hỏi liệu có một số lớp lót mà tôi có thể sử dụng để làm điều này thay thế không.


4
Tôi đang ném một ngoại lệ vào một trong các lớp học của mình, nhưng lớp học của tôi đang được sử dụng bởi một thư viện nuốt tất cả các ngoại lệ và ném các ngoại lệ của chính nó. Vấn đề là, ngoại lệ thư viện rất chung chung và tôi cần biết ngoại lệ cụ thể nào tôi đã ném để biết cách xử lý vấn đề. Để làm cho mọi thứ tồi tệ hơn, thư viện sẽ ném ngoại lệ của chính nó nhiều lần lồng vào nhau, đến một độ sâu tùy ý. Vì vậy, ví dụ, nó sẽ ném LibraryException -> LibraryException -> LibraryException -> MyException. Ngoại lệ của tôi luôn là ngoại lệ cuối cùng trên chuỗi và không có ngoại lệ bên trong của riêng nó.
Daniel T.

Ồ, tôi hiểu tại sao bạn có thể từ bỏ sâu thẳm nhất, đã tự mình làm như vậy (và các biến thể, chẳng hạn như trong cùng bắt nguồn từ một loại cụ thể) nhưng tôi không hiểu vấn đề là gì với những gì bạn đã có.
Jon Hanna

Ồ, tôi hiểu ý bạn. Tôi chỉ tự hỏi liệu có cách viết khác không. Tôi đã quá quen với việc sử dụng LINQđể làm gần như mọi thứ mà nó có vẻ kỳ lạ khi tôi phải viết một vòng lặp. Tôi chủ yếu làm việc với quyền truy cập dữ liệu vì vậy tôi làm việc khá nhiều với ICollectionshầu hết mọi thứ tôi làm.
Daniel T.

@Daniel, không phải LINQchỉ che giấu thực tế là mã về cơ bản vẫn phải lặp lại? Có bất kỳ lệnh dựa trên tập hợp thực sự nào trong .NET không?
Brad

6
Câu trả lời đúng nhất đang ẩn nấp gần cuối với (hiện tại) chỉ có 2 phiếu bầu - Exception.GetBaseException (). Điều này đã nằm trong khuôn khổ về cơ bản mãi mãi. Cảm ơn batCattle đã kiểm tra sự tỉnh táo.
Jay

Câu trả lời:


138

Lót :)

while (e.InnerException != null) e = e.InnerException;

Rõ ràng là bạn không thể làm cho nó đơn giản hơn được nữa.

Như đã nói trong câu trả lời này của Glenn McElhoe, đó là cách duy nhất đáng tin cậy.


7
Phương thức GetBaseException () thực hiện vòng lặp w / oa này. msdn.microsoft.com/en-us/library/…
codingoutloud

8
Điều này làm việc trong khi Exception.GetBaseException()không cho tôi.
Tarik

121

Tôi tin rằng Exception.GetBaseException()làm điều tương tự như các giải pháp này.

Lưu ý: Từ nhiều nhận xét khác nhau, chúng tôi đã phát hiện ra rằng không phải lúc nào cũng làm điều tương tự theo nghĩa đen và trong một số trường hợp, giải pháp lặp / lặp lại sẽ giúp bạn tiến xa hơn. Nó thường là ngoại lệ trong cùng, không nhất quán một cách đáng thất vọng, nhờ một số loại Ngoại lệ nhất định ghi đè mặc định. Tuy nhiên, nếu bạn nắm bắt được các loại ngoại lệ cụ thể và đảm bảo hợp lý rằng chúng không phải là những kẻ kỳ quặc (như AggregateException) thì tôi hy vọng nó sẽ nhận được ngoại lệ hợp pháp trong cùng / sớm nhất.


3
+1 Không có gì giống như biết BCL để tránh các giải pháp phức tạp. Rõ ràng, đây là câu trả lời chính xác nhất.
Jay

4
Vì một số lý do, GetBaseException()không trả về ngoại lệ gốc đầu tiên.
Tarik

4
Glenn McElhoe đã chỉ ra rằng thực sự GetBaseException () không phải lúc nào cũng làm được những gì MSDN gợi ý mà chúng ta có thể mong đợi nói chung (rằng "Exception là nguyên nhân gốc rễ của một hoặc nhiều ngoại lệ tiếp theo"). Trong AggregateException nó được giới hạn ở ngoại lệ thấp nhất của cùng loại và có lẽ còn có những ngoại lệ khác.
Josh Sutterfield

1
Không hoạt động cho sự cố của tôi. Tôi có một vài cấp độ thấp hơn những gì điều này đang cung cấp.
Giovanni B

2
GetBaseException()không hoạt động đối với tôi, vì ngoại lệ hàng đầu là AggregateException.
Chris Ballance

21

Vòng qua Nội ngoại lệ là cách duy nhất đáng tin cậy.

Nếu ngoại lệ bị bắt là AggregateException, thì GetBaseException()chỉ trả về AggregateException trong cùng.

http://msdn.microsoft.com/en-us/library/system.aggregateexception.getbaseexception.aspx


1
Nắm bắt tốt. Cảm ơn - điều đó giải thích các nhận xét ở trên mà câu trả lời đó không hoạt động đối với một số người. Điều này làm rõ rằng mô tả chung của MSDN về "nguyên nhân gốc rễ của một hoặc nhiều trường hợp ngoại lệ tiếp theo" không phải lúc nào bạn cũng giả định vì các lớp dẫn xuất có thể ghi đè nó. Quy tắc thực sự dường như là tất cả các ngoại lệ trong chuỗi ngoại lệ "đồng ý" trên GetBaseException () & trả về cùng một đối tượng, có vẻ như trường hợp của AggregateException.
Josh Sutterfield

12

Nếu bạn không biết các ngoại lệ bên trong được lồng vào nhau sâu đến mức nào, thì không có cách nào xoay quanh vòng lặp hoặc đệ quy.

Tất nhiên, bạn có thể xác định một phương thức mở rộng tóm tắt điều này:

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

Tôi biết đây là một bài đăng cũ, nhưng tôi ngạc nhiên là không ai đề xuất GetBaseException()phương thức nào trên Exceptionlớp:

catch (Exception x)
{
    var baseException = x.GetBaseException();
}

Điều này đã có từ .NET 1.1. Tài liệu tại đây: http://msdn.microsoft.com/en-us/library/system.exception.getbaseexception(v=vs.71).aspx


Quả thực đã được chỉ ra trước đó vào ngày 5 tháng 12 năm 2012
armadillo.mx Ngày

2

Đôi khi bạn có thể có nhiều ngoại lệ bên trong (nhiều ngoại lệ sôi nổi). Trong trường hợp đó bạn có thể muốn làm:

List<Exception> es = new List<Exception>();
while(e.InnerException != null)
{
   es.add(e.InnerException);
   e = e.InnerException
}

1

Không hoàn toàn một dòng nhưng gần:

        Func<Exception, Exception> last = null;
        last = e => e.InnerException == null ? e : last(e.InnerException);

1

Trong thực tế, rất đơn giản, bạn có thể sử dụng Exception.GetBaseException()

Try
      //Your code
Catch ex As Exception
      MessageBox.Show(ex.GetBaseException().Message, My.Settings.MsgBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
End Try

Cảm ơn giải pháp của bạn! Rất ít người nghĩ rằng trong VB: D
Federico Navarrete

1
Và có một lý do tại sao! : P
Yoda

1

Bạn phải lặp lại, và phải lặp lại, việc chuyển vòng lặp thành một chức năng riêng biệt sẽ rõ ràng hơn.

Tôi đã tạo một phương pháp mở rộng để giải quyết vấn đề này. Nó trả về danh sách tất cả các ngoại lệ bên trong của kiểu được chỉ định, đuổi theo Exception.InnerException và AggregateException.InnerExceptions.

Trong vấn đề cụ thể của tôi, việc theo đuổi các ngoại lệ bên trong phức tạp hơn bình thường, bởi vì các ngoại lệ đang được ném bởi các hàm tạo của các lớp đang được gọi thông qua phản xạ. Ngoại lệ mà chúng tôi đang bắt gặp có InnerException kiểu TargetInvocationException và các ngoại lệ mà chúng tôi thực sự cần xem xét đã bị chôn sâu trong cây.

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

Cách sử dụng khá đơn giản. Ví dụ: nếu bạn muốn biết liệu chúng tôi có gặp phải

catch (Exception ex)
{
    var myExes = ex.innerExceptions<MyException>();
    if (myExes.Any(x => x.Message.StartsWith("Encountered my specific error")))
    {
        // ...
    }
}

Về cái gì Exception.GetBaseException()?
Kiquenet

1

Bạn có thể sử dụng đệ quy để tạo một phương thức trong một lớp tiện ích ở đâu đó.

public Exception GetFirstException(Exception ex)
{
    if(ex.InnerException == null) { return ex; } // end case
    else { return GetFirstException(ex.InnerException); } // recurse
}

Sử dụng:

try
{
    // some code here
}
catch (Exception ex)
{
    Exception baseException = GetFirstException(ex);
}

Phương pháp tiện ích mở rộng được đề xuất (ý kiến ​​hay @dtb)

public static Exception GetFirstException(this Exception ex)
{
    if(ex.InnerException == null) { return ex; } // end case
    else { return GetFirstException(ex.InnerException); } // recurse
}

Sử dụng:

try
{
    // some code here
}
catch (Exception ex)
{
    Exception baseException = ex.GetFirstException();
}

Hữu ích public static Exception GetOriginalException(this Exception ex) { if (ex.InnerException == null) return ex; return ex.InnerException.GetOriginalException(); }
Kiquenet

Không áp dụng AggregateException.InnerExceptions?
Kiquenet

0

Một cách khác bạn có thể làm là gọi GetBaseException()hai lần:

Exception innermostException = e.GetBaseException().GetBaseException();

Điều này hoạt động bởi vì nếu nó là một AggregateException, cuộc gọi đầu tiên đưa AggregateExceptionbạn đến ngoại lệ trong cùng thì cuộc gọi thứ hai đưa bạn đến ngoại lệ trong cùng của ngoại lệ đó. Nếu ngoại lệ đầu tiên không phải là một AggregateException, thì cuộc gọi thứ hai chỉ trả về cùng một ngoại lệ.


0

Tôi gặp phải vấn đề này và muốn có thể liệt kê tất cả các thông báo ngoại lệ từ "ngăn xếp" ngoại lệ. Vì vậy, tôi đã nghĩ ra điều này.

public static string GetExceptionMessages(Exception ex)
{
    if (ex.InnerException is null)
        return ex.Message;
    else return $"{ex.Message}\n{GetExceptionMessages(ex.InnerException)}";
}
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 cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.