2008-09-11 29 views
219

Tôi đang gọi, thông qua sự phản chiếu, một phương pháp có thể gây ra một ngoại lệ. Làm thế nào tôi có thể vượt qua ngoại lệ cho người gọi của tôi mà không có sự phản ánh wrapper đặt xung quanh nó? Tôi đang rethrowing InnerException, nhưng điều này phá hủy các dấu vết stack. mã ví dụ:Trong C#, làm thế nào tôi có thể tính toán lại InnerException mà không làm mất dấu vết ngăn xếp?

public void test1() 
    { 
     // Throw an exception for testing purposes 
     throw new ArgumentException("test1"); 
    } 

    void test2() 
    { 
     try 
     { 
      MethodInfo mi = typeof(Program).GetMethod("test1"); 
      mi.Invoke(this, null); 
     } 
     catch (TargetInvocationException tiex) 
     { 
      // Throw the new exception 
      throw tiex.InnerException; 
     } 
    } 
+1

Có một cách khác để thực hiện việc này không yêu cầu bất kỳ voodoo nào. Hãy xem câu trả lời ở đây: http://stackoverflow.com/questions/15668334/preserving-exceptions-from-dynamically-invoked-methods –

+0

Ngoại lệ được ném trong phương pháp được gọi là động là ngoại lệ bên trong của "Ngoại lệ có được ném bởi mục tiêu của một lời kêu gọi "ngoại lệ. Nó có dấu vết ngăn xếp của riêng nó. Có thực sự là không có nhiều người khác phải lo lắng về. – ajeh

Trả lời

334

Trong .NET 4.5 hiện nay là lớp ExceptionDispatchInfo.

này cho phép bạn chụp một ngoại lệ và tái ném nó mà không thay đổi stack-trace:

try 
{ 
    task.Wait(); 
} 
catch(AggregateException ex) 
{ 
    ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); 
} 

này hoạt động trên bất kỳ ngoại lệ, không chỉ AggregateException.

Được giới thiệu do tính năng ngôn ngữ C# await C#, mở khóa các ngoại lệ bên trong từ AggregateException trường hợp để làm cho các tính năng ngôn ngữ không đồng bộ giống với các tính năng ngôn ngữ đồng bộ.

+6

Ứng cử viên tốt cho một phương pháp mở rộng Exception.Rethrow()? – nmarler

+8

Lưu ý rằng lớp ExceptionDispatchInfo nằm trong vùng tên System.Runtime.ExceptionServices và không có sẵn trước .NET 4.5. – yoyo

+28

Bạn có thể cần phải đặt một thường xuyên 'ném;' sau dòng .Throw(), bởi vì trình biên dịch sẽ không biết rằng .Throw() luôn luôn ném một ngoại lệ. 'throw;' sẽ không bao giờ được gọi là kết quả, nhưng ít nhất trình biên dịch sẽ không phàn nàn nếu phương thức của bạn yêu cầu một đối tượng trả về hoặc là một hàm async. – Todd

31

Tôi nghĩ rằng đặt cược tốt nhất của bạn sẽ được chỉ cần đặt này trong khối catch của bạn:

throw; 

Và sau đó trích xuất các InnerException sau.

+14

Hoặc xóa hoàn toàn thử/bắt. –

+3

@Earwicker. Loại bỏ try/catch không phải là một giải pháp tốt nói chung vì nó bỏ qua các trường hợp cần mã dọn dẹp trước khi truyền bá ngoại lệ lên ngăn xếp cuộc gọi. – Jordan

+8

@Jordan - Dọn dẹp mã phải nằm trong khối cuối cùng không phải là khối catch – Paolo

11

phản ánh Thậm chí nhiều hơn ...

catch (TargetInvocationException tiex) 
{ 
    // Get the _remoteStackTraceString of the Exception class 
    FieldInfo remoteStackTraceString = typeof(Exception) 
     .GetField("_remoteStackTraceString", 
      BindingFlags.Instance | BindingFlags.NonPublic); // MS.Net 

    if (remoteStackTraceString == null) 
     remoteStackTraceString = typeof(Exception) 
     .GetField("remote_stack_trace", 
      BindingFlags.Instance | BindingFlags.NonPublic); // Mono 

    // Set the InnerException._remoteStackTraceString 
    // to the current InnerException.StackTrace 
    remoteStackTraceString.SetValue(tiex.InnerException, 
     tiex.InnerException.StackTrace + Environment.NewLine); 

    // Throw the new exception 
    throw tiex.InnerException; 
} 

Hãy ghi nhớ rằng điều này có thể phá vỡ bất cứ lúc nào, như lĩnh vực tư nhân không phải là một phần của API. Xem thêm thảo luận về Mono bugzilla.

+27

Đây thực sự là một ý tưởng tồi, vì nó phụ thuộc vào các chi tiết không có giấy tờ nội bộ về các lớp khung công tác. –

+0

@Earwicker: +1 đây là một ý tưởng tồi tệ xấu –

+1

Hóa ra, có thể giữ lại dấu vết ngăn xếp mà không có Sự phản chiếu, xem bên dưới. –

10

Đầu tiên: không bị mất TargetInvocationException - đó là thông tin giá trị khi bạn muốn gỡ lỗi mọi thứ.
Thứ hai: Gói TIE là InnerException trong loại ngoại lệ của riêng bạn và đặt thuộc tính OriginalException liên kết đến những gì bạn cần (và giữ nguyên toàn bộ callstack).
Thứ ba: Hãy để bong bóng TIE ra khỏi phương pháp của bạn.

11
public static class ExceptionHelper 
{ 
    private static Action<Exception> _preserveInternalException; 

    static ExceptionHelper() 
    { 
     MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic); 
     _preserveInternalException = (Action<Exception>)Delegate.CreateDelegate(typeof(Action<Exception>), preserveStackTrace);    
    } 

    public static void PreserveStackTrace(this Exception ex) 
    { 
     _preserveInternalException(ex); 
    } 
} 

Gọi phương thức mở rộng ngoại lệ của bạn trước khi bạn ném nó, nó sẽ giữ nguyên dấu vết ngăn xếp ban đầu.

+0

Hãy lưu ý rằng trong .Net 4.0, InternalPreserveStackTrace bây giờ là một no-op - nhìn vào Reflector và bạn sẽ thấy phương thức hoàn toàn trống rỗng! –

+0

Scratch rằng: Tôi đã nhìn vào RC: trong phiên bản beta, họ đã đưa việc thực hiện trở lại một lần nữa! –

+3

gợi ý: thay đổi PreserveStackTrace để trả về ex - sau đó để ném một ngoại lệ, bạn chỉ có thể nói: ném ex.PreserveStackTrace(); –

5

Các bạn, bạn thật tuyệt .. Tôi sẽ sớm trở thành một necromancer.

public void test1() 
    { 
     // Throw an exception for testing purposes 
     throw new ArgumentException("test1"); 
    } 

    void test2() 
    { 
      MethodInfo mi = typeof(Program).GetMethod("test1"); 
      ((Action)Delegate.CreateDelegate(typeof(Action), mi))(); 

    } 
+1

Ý tưởng hay, nhưng bạn không phải lúc nào cũng kiểm soát mã gọi '.Invoke()'. –

+1

Và bạn không phải lúc nào cũng biết các loại đối số/kết quả tại thời gian biên dịch. –

84

có thể để bảo vệ vết đống trước rethrowing mà không cần suy nghĩ:

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 
} 

này lãng phí rất nhiều chu kỳ so với gọi InternalPreserveStackTrace qua đại biểu lưu trữ, nhưng có lợi thế là chỉ dựa về chức năng công cộng. Dưới đây là một vài mẫu sử dụng phổ biến cho các chức năng bảo quản ngăn xếp dấu vết:

// usage (A): cross-thread invoke, messaging, custom task schedulers etc. 
catch (Exception e) 
{ 
    PreserveStackTrace (e) ; 

    // store exception to be re-thrown later, 
    // possibly in a different thread 
    operationResult.Exception = e ; 
} 

// usage (B): after calling MethodInfo.Invoke() and the like 
catch (TargetInvocationException tiex) 
{ 
    PreserveStackTrace (tiex.InnerException) ; 

    // unwrap TargetInvocationException, so that typed catch clauses 
    // in library/3rd-party code can work correctly; 
    // new stack trace is appended to existing one 
    throw tiex.InnerException ; 
} 
+0

Có vẻ thú vị, những gì cần phải xảy ra sau khi chạy các chức năng này? – vdboor

+0

@vdboor: Tôi không hiểu câu hỏi của bạn. Bản chỉnh sửa có làm rõ mọi thứ không? –

+2

Thực ra, nó không chậm hơn nhiều so với việc gọi 'InternalPreserveStackTrace' (chậm hơn khoảng 6% với 10000 lần lặp). Truy cập trực tiếp vào các trường bằng cách phản ánh nhanh hơn khoảng 2,5% so với việc gọi 'InternalPreserveStackTrace' –

3

Mã mẫu khác sử dụng tuần tự/deserialization ngoại lệ. Nó không yêu cầu loại ngoại lệ thực tế được serializable. Ngoài ra, nó chỉ sử dụng các phương thức công cộng/được bảo vệ.

static void PreserveStackTrace(Exception e) 
    { 
     var ctx = new StreamingContext(StreamingContextStates.CrossAppDomain); 
     var si = new SerializationInfo(typeof(Exception), new FormatterConverter()); 
     var ctor = typeof(Exception).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(SerializationInfo), typeof(StreamingContext) }, null); 

     e.GetObjectData(si, ctx); 
     ctor.Invoke(e, new object[] { si, ctx }); 
    } 
4

Không ai giải thích sự khác biệt giữa ExceptionDispatchInfo.Capture(ex).Throw() và đồng bằng throw, vì vậy tại đây.

Cách hoàn chỉnh để tính lại ngoại lệ bị bắt là sử dụng ExceptionDispatchInfo.Capture(ex).Throw() (chỉ khả dụng từ .Net 4.5).

Dưới đây có những 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 theo dõi ngăn xếp nơi số dòng mã nguồn cho phương pháp CallingMethod là số dòng của dòng throw new Exception("TEST").

Tuy nhiên, trường hợp 3 sẽ cung cấp cho bạn theo dõi ngăn xếp nơi số dòng mã nguồn cho phương thức CallingMethod là số dòng của cuộc gọi throw. Điều này có nghĩa rằng nếu đường dây throw new Exception("TEST") được bao quanh bởi các hoạt động khác, bạn không có ý tưởng về số dòng mà ngoại lệ đã thực sự được 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 thực tế rethrow vì nó thay đổi loại ngoại lệ ban đầu.

+1

Tôi luôn nghĩ rằng 'ném' đã không đặt lại stacktrace (trái với 'ném e'). –

Các vấn đề liên quan