Thực hành tốt nhất để bắt và ném lại ngoại lệ .NET


284

Các thực hành tốt nhất để xem xét khi bắt ngoại lệ và ném lại chúng là gì? Tôi muốn đảm bảo rằng dấu vết Exceptioncủa đối tượng InnerExceptionvà ngăn xếp được bảo tồn. Có sự khác biệt giữa các khối mã sau trong cách họ xử lý việc này không?

try
{
    //some code
}
catch (Exception ex)
{
    throw ex;
}

Vs:

try
{
    //some code
}
catch
{
    throw;
}

Câu trả lời:


262

Cách để lưu giữ dấu vết ngăn xếp là thông qua việc sử dụng throw;Điều này cũng hợp lệ

try {
  // something that bombs here
} catch (Exception ex)
{
    throw;
}

throw ex;về cơ bản giống như ném một ngoại lệ từ thời điểm đó, vì vậy dấu vết ngăn xếp sẽ chỉ đi đến nơi bạn đang đưa ra throw ex;tuyên bố.

Mike cũng đúng, giả sử ngoại lệ cho phép bạn vượt qua một ngoại lệ (được khuyến nghị).

Karl Seguin có một bài viết tuyệt vời về xử lý ngoại lệ trong các nền tảng của ông về lập trình sách điện tử , đó là một cuốn sách tuyệt vời để đọc.

Chỉnh sửa: Liên kết làm việc với Cơ sở lập trình pdf. Chỉ cần tìm kiếm văn bản cho "ngoại lệ".


10
Tôi không chắc chắn liệu bài viết đó có tuyệt vời hay không, nó gợi ý thử {// ...} Catch (Exception ex) {ném Exception mới (ex.Message + "other Stuff"); } tốt. Vấn đề là bạn hoàn toàn không thể xử lý ngoại lệ đó thêm nữa, trừ khi bạn bắt được tất cả các ngoại lệ, một điều không nên lớn (bạn có chắc chắn muốn xử lý OutOfMemoryException không?)
ljs

2
@ljs Bài viết đã thay đổi kể từ khi bình luận của bạn vì tôi không thấy bất kỳ phần nào mà anh ấy đề xuất. Thực tế hoàn toàn ngược lại, anh ta nói không nên làm điều đó và hỏi bạn có muốn xử lý OutOfMemoryException không!?
RyanfaeScotland

6
Đôi khi ném; là không đủ để bảo tồn dấu vết ngăn xếp. Dưới đây là một ví dụ https://dotnetfiddle.net/CkMFoX
Artavazd Balayan

4
Hoặc ExceptionDispatchInfo.Capture(ex).Throw(); throw;trong .NET +4.5 stackoverflow.com/questions/57383/
Alfred Wallace

Giải pháp @AlfredWallace hoạt động hoàn hảo với tôi. thử {...} bắt {throw} không lưu giữ dấu vết ngăn xếp. Cảm ơn bạn.
atownson

100

Nếu bạn ném một ngoại lệ mới với ngoại lệ ban đầu, bạn cũng sẽ giữ nguyên dấu vết ngăn xếp ban đầu ..

try{
} 
catch(Exception ex){
     throw new MoreDescriptiveException("here is what was happening", ex);
}

Không có vấn đề gì, tôi cố gắng ném Ngoại lệ mới ("tin nhắn", ex) luôn ném ex và bỏ qua thông báo tùy chỉnh. ném ngoại lệ mới ("tin nhắn", ex.InnerException) hoạt động mặc dù.
Tod

Nếu không có ngoại lệ tùy chỉnh là cần thiết, người ta có thể sử dụng AggregateException (.NET 4+) msdn.microsoft.com/en-us/l
Library / trộm

AggregateExceptionchỉ nên được sử dụng cho các trường hợp ngoại lệ trên các hoạt động tổng hợp. Ví dụ, nó được ném bởi ParallelEnumerableTaskcác lớp của CLR. Cách sử dụng có lẽ nên theo ví dụ này.
Aluan Haddad

29

Trên thực tế, có một số tình huống mà thống kê throwsẽ không lưu giữ thông tin StackTrace. Ví dụ: trong mã dưới đây:

try
{
  int i = 0;
  int j = 12 / i; // Line 47
  int k = j + 1;
}
catch
{
  // do something
  // ...
  throw; // Line 54
}

StackTrace sẽ chỉ ra rằng dòng 54 đã đưa ra ngoại lệ, mặc dù nó được nâng lên ở dòng 47.

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at Program.WithThrowIncomplete() in Program.cs:line 54
   at Program.Main(String[] args) in Program.cs:line 106

Trong các tình huống như mô tả ở trên, có hai tùy chọn để dự đoán StackTrace ban đầu:

Gọi Exception.IternalPreserveStackTrace

Vì nó là một phương thức riêng tư, nó phải được gọi bằng cách sử dụng sự phản chiếu:

private static void PreserveStackTrace(Exception exception)
{
  MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
    BindingFlags.Instance | BindingFlags.NonPublic);
  preserveStackTrace.Invoke(exception, null);
}

Tôi có một bất lợi là dựa vào một phương thức riêng tư để lưu giữ thông tin StackTrace. Nó có thể được thay đổi trong các phiên bản tương lai của .NET Framework. Ví dụ mã ở trên và giải pháp đề xuất bên dưới được trích xuất từ weblog Fabrice MARGUERIE .

Gọi ngoại lệ.SetObjectData

Kỹ thuật dưới đây được Anton Tykhyy đề xuất là câu trả lời cho Trong C #, làm thế nào tôi có thể chia sẻ lại InternalException mà không mất câu hỏi theo dõi ngăn xếp .

static void PreserveStackTrace (Exception e) 
{ 
  var ctx = new StreamingContext  (StreamingContextStates.CrossAppDomain) ; 
  var mgr = new ObjectManager     (null, ctx) ; 
  var si  = new SerializationInfo (e.GetType (), new FormatterConverter ()) ; 

  e.GetObjectData    (si, ctx)  ; 
  mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData 
  mgr.DoFixups       ()         ; // ObjectManager calls SetObjectData 

  // voila, e is unmodified save for _remoteStackTraceString 
} 

Mặc dù, nó có lợi thế là chỉ dựa vào các phương thức công khai, nhưng nó cũng phụ thuộc vào hàm tạo ngoại lệ sau (một số trường hợp ngoại lệ do bên thứ 3 phát triển không thực hiện):

protected Exception(
    SerializationInfo info,
    StreamingContext context
)

Trong tình huống của tôi, tôi đã phải chọn cách tiếp cận đầu tiên, bởi vì các ngoại lệ được đưa ra bởi thư viện của bên thứ 3 mà tôi đang sử dụng đã không triển khai hàm tạo này.


1
Bạn có thể bắt ngoại lệ và xuất bản ngoại lệ này bất cứ nơi nào bạn muốn. Sau đó ném một cái mới giải thích những gì đã xảy ra với người dùng. Bằng cách này bạn có thể thấy những gì đã xảy ra tại thời điểm hiện tại ngoại lệ bị bắt, người dùng có thể bất cẩn ngoại lệ thực sự là gì.
Çöđěxěŕ

2
Với .NET 4.5, có một tùy chọn thứ ba và - theo ý kiến ​​của tôi - dọn dẹp: sử dụng ExceptionDispatchInfo. Xem Tragedians trả lời cho một câu hỏi liên quan ở đây: stackoverflow.com/a/17091351/567000 để biết thêm.
Søren Boisen

20

Khi bạn throw ex, về cơ bản bạn đang ném một ngoại lệ mới và sẽ bỏ lỡ thông tin theo dõi ngăn xếp ban đầu. throwlà phương pháp ưa thích.


13

Nguyên tắc chung là tránh Bắt và Ném Exceptionđối tượng cơ bản . Điều này buộc bạn phải thông minh hơn một chút về các ngoại lệ; nói cách khác, bạn nên nắm bắt rõ ràng để SqlExceptionmã xử lý của bạn không làm gì sai với a NullReferenceException.

Tuy nhiên, trong thế giới thực, bắt và ghi lại ngoại lệ cơ sở cũng là một cách làm tốt, nhưng đừng quên đi bộ toàn bộ để có được bất kỳ thứ gì InnerExceptionsnó có thể có.


2
Tôi nghĩ rằng tốt nhất là xử lý các trường hợp ngoại lệ chưa được xử lý cho mục đích ghi nhật ký bằng cách sử dụng các ngoại lệ AppDomain.CienDomain.UnhandledException và Application.ThreadException. Sử dụng các khối lớn {...} Catch (Exception ex) {...} ở khắp mọi nơi có nghĩa là rất nhiều sự trùng lặp. Phụ thuộc vào việc bạn có muốn ghi nhật ký ngoại lệ được xử lý hay không, trong trường hợp đó (ít nhất là tối thiểu) việc sao chép có thể là không thể tránh khỏi.
ljs

Ngoài ra sử dụng những phương tiện sự kiện mà bạn làm đăng nhập tất cả các trường hợp ngoại lệ unhandled, trong khi đó nếu bạn sử dụng ol lớn' try {...} catch (Exception ex) {...} khối bạn có thể bỏ lỡ một số.
ljs

10

Bạn nên luôn luôn sử dụng "ném;" để suy nghĩ lại các ngoại lệ trong .NET,

Tham khảo điều này, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

Về cơ bản MSIL (CIL) có hai hướng dẫn - "ném" và "nghĩ lại":

  • "Ném ex;" của C # được biên dịch thành "cú ném" của MSIL
  • "Ném;" của C # - vào MSIL "nghĩ lại"!

Về cơ bản tôi có thể thấy lý do tại sao "throw ex" ghi đè dấu vết stack.


Liên kết - tốt, thực sự là nguồn mà liên kết trích dẫn - có đầy đủ thông tin tốt và cũng lưu ý một thủ phạm có thể xảy ra cho lý do tại sao nhiều người nghĩ throw ex;sẽ nghĩ lại - trong Java, đúng vậy! Nhưng bạn nên bao gồm thông tin đó ở đây để có câu trả lời hạng A. (Mặc dù tôi vẫn đang bắt kịp ExceptionDispatchInfo.Capturecâu trả lời từ jeuoekdcwzfwccu .)
ruffin

10

Không ai giải thích được sự khác biệt giữa ExceptionDispatchInfo.Capture( ex ).Throw()và đồng bằng throw, vì vậy đây là. Tuy nhiên, một số người đã nhận thấy vấn đề với throw.

Cách hoàn chỉnh để lấy lại một ngoại lệ bị bắt là sử dụng ExceptionDispatchInfo.Capture( ex ).Throw()(chỉ có sẵn từ .Net 4.5).

Dưới đây là các trường hợp cần thiết để kiểm tra điều này:

1.

void CallingMethod()
{
    //try
    {
        throw new Exception( "TEST" );
    }
    //catch
    {
    //    throw;
    }
}

2.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        ExceptionDispatchInfo.Capture( ex ).Throw();
        throw; // So the compiler doesn't complain about methods which don't either return or throw.
    }
}

3.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch
    {
        throw;
    }
}

4.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        throw new Exception( "RETHROW", ex );
    }
}

Trường hợp 1 và trường hợp 2 sẽ cung cấp cho bạn một dấu vết ngăn xếp trong đó số dòng mã nguồn cho CallingMethodphương thức là số dòng của throw new Exception( "TEST" )dòng.

Tuy nhiên, trường hợp 3 sẽ cung cấp cho bạn một dấu vết ngăn xếp trong đó số dòng mã nguồn cho CallingMethodphương thức là số dòng của throwcuộc gọi. Điều này có nghĩa là nếu throw new Exception( "TEST" )dòng được bao quanh bởi các hoạt động khác, bạn không biết số dòng nào thực sự bị ném.

Trường hợp 4 tương tự với trường hợp 2 vì số dòng của ngoại lệ ban đầu được giữ nguyên, nhưng không phải là một suy nghĩ thực sự vì nó thay đổi loại ngoại lệ ban đầu.


Thêm một blurb đơn giản để không bao giờ sử dụng throw ex;và đây là câu trả lời tốt nhất của tất cả chúng.
NH.

8

Một số người thực sự đã bỏ lỡ một điểm rất quan trọng - 'ném' và 'ném ex' có thể làm điều tương tự nhưng họ không cung cấp cho bạn một thông tin quan trọng, đó là dòng xảy ra ngoại lệ.

Hãy xem xét các mã sau đây:

static void Main(string[] args)
{
    try
    {
        TestMe();
    }
    catch (Exception ex)
    {
        string ss = ex.ToString();
    }
}

static void TestMe()
{
    try
    {
        //here's some code that will generate an exception - line #17
    }
    catch (Exception ex)
    {
        //throw new ApplicationException(ex.ToString());
        throw ex; // line# 22
    }
}

Khi bạn thực hiện 'ném' hoặc 'ném ex', bạn sẽ nhận được dấu vết ngăn xếp nhưng dòng # sẽ là # 22 vì vậy bạn không thể biết chính xác dòng nào đang ném ngoại lệ (trừ khi bạn chỉ có 1 hoặc vài các dòng mã trong khối thử). Để có được dòng mong đợi # 17 trong ngoại lệ của bạn, bạn sẽ phải ném một ngoại lệ mới với dấu vết ngăn xếp ngoại lệ ban đầu.


3

Bạn cũng có thể sử dụng:

try
{
// Dangerous code
}
finally
{
// clean up, or do nothing
}

Và bất kỳ trường hợp ngoại lệ nào được ném sẽ bong bóng lên cấp độ tiếp theo xử lý chúng.


3

Tôi chắc chắn sẽ sử dụng:

try
{
    //some code
}
catch
{
    //you should totally do something here, but feel free to rethrow
    //if you need to send the exception up the stack.
    throw;
}

Điều đó sẽ bảo vệ ngăn xếp của bạn.


1
Để công bằng để vượt qua tôi vào năm 2008, OP đã hỏi cách bảo quản ngăn xếp - và năm 2008 tôi đã đưa ra một câu trả lời đúng. Điều còn thiếu trong câu trả lời của tôi là một phần của việc thực sự làm gì đó trong việc nắm bắt.
1kevgriff

@JohnSaunders Điều đó đúng nếu và chỉ khi bạn không làm gì trước đó throw; ví dụ bạn có thể dọn sạch một lần dùng một lần (trong đó bạn CHỈ gọi nó là một lỗi) và sau đó ném ngoại lệ.
Meirion Hughes

@meirion khi tôi viết bình luận không có gì trước khi ném. Khi nó được thêm vào, tôi đã nâng cấp, nhưng không xóa bình luận.
John Saunders

0

FYI Tôi vừa kiểm tra cái này và dấu vết ngăn xếp được báo cáo bởi 'throw;' không phải là một dấu vết ngăn xếp hoàn toàn chính xác. Thí dụ:

    private void foo()
    {
        try
        {
            bar(3);
            bar(2);
            bar(1);
            bar(0);
        }
        catch(DivideByZeroException)
        {
            //log message and rethrow...
            throw;
        }
    }

    private void bar(int b)
    {
        int a = 1;
        int c = a/b;  // Generate divide by zero exception.
    }

Theo dõi ngăn xếp chỉ ra nguồn gốc của ngoại lệ một cách chính xác (số dòng được báo cáo) nhưng số dòng được báo cáo cho foo () là dòng ném; câu lệnh, do đó bạn không thể biết được cuộc gọi nào đến bar () gây ra ngoại lệ.


Đó là lý do tại sao tốt nhất không nên cố gắng bắt ngoại lệ trừ khi bạn có kế hoạch làm điều gì đó với họ
Nate Zaugg
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.