簡體   English   中英

有沒有真正的世界理由使用throw ex?

[英]Is there any real world reason to use throw ex?

在C#中, throw ex幾乎總是錯誤的,因為它會重置堆棧跟蹤。

我只是想知道,這有什么現實世界嗎? 我能想到的唯一原因是隱藏你的封閉庫的內部,但這是一個非常薄弱的​​原因。 除此之外,我從未在現實世界中遇到過。

編輯:我的意思是throw ex ,就像拋出完全相同的異常,但是使用空堆棧跟蹤,就像完全錯誤一樣。 我知道throw ex必須作為一個語言構造存在,以允許拋出一個不同的異常( throw new DifferentException("ex as innerException", ex) ),並且只是想知道是否存在一個throw ex沒有錯誤的throw new DifferentException("ex as innerException", ex)

我從未見過它是故意使用的,但那當然沒有任何意義。

讓我們來看看替代方案:

catch(Exception ex)
{
    // do stuff (logging)
    throw;                                // a) continue ex
    throw new SomeException("blah", ex);  // b) wrap
    throw new SomeException("blah");      // c) replace
    throw ex;                             // d) reset stack-trace
}

c)和d)都在放棄堆棧跟蹤,d)唯一的“優勢”我可以看到throw ex; 保留ex的確切類型和可能的額外屬性(SQL錯誤)。

那么有沒有一種情況你想要保留除堆棧跟蹤之外的所有異常信息? 不正常,但有些猜測:

  • 在模糊庫的邊界。 但c)在這里看起來更好。
  • 在調用站點上的一些生成代碼,就像在RegEx類中一樣。

如果你停下來思考它, throw ex實際上經常使用 - 除了一個未命名的局部變量。 (重新)設計語言將非常困難,因此:

throw new ArgumentException();

是有效的,但是

var ex = new ArgumentException();
throw ex;

無效(然后擴展它以跟蹤更復雜的變量賦值)。 throw ex是throw語句的“正常”形式,但是在重新拋出已經捕獲的異常時通常是錯誤的。

沒有正當理由重新拋出異常對象,即'throw ex'。

這是一種語言\\編譯器功能,雖然在實踐中沒有增加任何價值,但卻沒有增加任何價值。 這是一種類似的情況,能夠編寫代碼來訪問空引用的成員 - 它當然可能但沒有價值,例如:

    //Possible but not useful.
    object ref1 = null;

    ref1.ToString();

能夠重新拋出異常對象是不幸的,並且經常被新手和有經驗的編碼人員誤解,直到你在生產中得到一個未處理的異常並記錄了截斷的堆棧跟蹤。

有一種微弱的感覺,它可以用來故意隱藏堆棧跟蹤信息,但拋出一個新的異常將是正確的方法來做到這一點。

我甚至可以說能夠重新拋出異常對象(即'throw ex')是一個設計缺陷 - 這樣做太容易做錯了。 但是,我懷疑這是出於性能原因而在編譯器中進行設計權衡,因為它會產生開銷來驗證'throw ex'不是're-throw'。 我敢肯定有很多這樣的權衡。

throw ex只是一個錯誤或由不知道或忘記throw;人做的錯字 throw;

它可能僅在一種情況下,在其答案中由其他人指出:您不希望將正確的堆棧跟蹤發送給調用者的情況,但您仍希望保留先前拋出的異常類型。 在實踐中,我很難想象有人會需要它的情況,並且不會使用throw new SomeCustomException(...)

FxCop規則CA2200 RethrowToPreserveStackDetails以相同的方式進行,邀請您使用throw;正確地重新拋出異常 throw;

try
{
    // ...
}
catch (SomeException ex)
{
    Logger.AddException(ex);
    throw;
}

以下是重新拋出現有異常對象的兩個實際應用程序:

反射

通過反射調用代碼時,拋出的任何異常都將包含在TargetInvocationException中 但是你可能想要/需要處理反射“障礙”之外的異常。 在這種情況下,包裝將使異常處理代碼復雜化。 相反,您可以打開原始異常並重新拋出。

如果您使用的是.NET 4.5+,則可以使用ExceptionDispatchInfo來保留原始堆棧跟蹤:

MethodInfo someMethod = ...
try
{
    someMethod.Invoke();
}
catch (TargetInvocationException e)
{
    ExceptionDispatchInfo.Capture(e.InnerException).Throw();
}

任務API

在.NET Task API中,在執行任務期間拋出的異常包含在AggregateException中 當使用async / await時,框架將在封面下執行展開,以便拋出原始異常而不是包裝器AggregateException。

除了在自動解包時需要重新拋出現有異常的BCL代碼之外,有些情況下您無法使用async / await,因此需要手動解包和重新拋出。 對於這一個實際的例子,檢查出的內部使用TaskExtensions.WaitAndUnwrapExceptionAsyncContext斯蒂芬Clearys優秀的執行AsyncEx項目。

您可能希望記錄異常,記錄它然后將其作為'throw ex'拋出到上層。 您可能還希望在此層中進行日志記錄,但這可能與您獲得異常的層無關,或者您可能不需要異常的詳細信息,因為您已經記錄了它。

讓我們說你有:

Public int GetAllCustomers()
{
     try
     {
          return data.GetAllCustomers()
     }
     catch()
     {
         Logger.log("Error calling GetAllCustomers");
     }
}
     //data
Public static int GetAllCustomers()
{
     try
     {
          //db operations...
     }
     catch(Exception ex)
     {
          Logger.log(ex.Stacktrace);
          throw ex;
     }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM