แนวทางปฏิบัติที่ดีที่สุดสำหรับการจับและยกเว้นข้อยกเว้น. NET


284

อะไรคือการปฏิบัติที่ดีที่สุดที่จะต้องพิจารณาเมื่อจับข้อยกเว้นและอีกครั้งโยนพวกเขา? ฉันต้องการตรวจสอบให้แน่ใจว่าExceptionวัตถุInnerExceptionและการติดตามสแต็กถูกเก็บรักษาไว้ มีความแตกต่างระหว่างบล็อครหัสต่อไปนี้ในวิธีที่พวกเขาจัดการกับเรื่องนี้หรือไม่?

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

Vs:

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

คำตอบ:


262

วิธีที่จะเก็บสแต็กการติดตามคือผ่านการใช้สิ่งthrow;นี้ใช้ได้เช่นกัน

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

throw ex;เป็นพื้นเช่นการขว้างปายกเว้นจากจุดนั้นเพื่อให้กองติดตามเท่านั้นที่จะไปที่ที่คุณจะออกthrow ex;คำสั่ง

ไมค์นั้นถูกต้องสมมติว่าข้อยกเว้นอนุญาตให้คุณผ่านข้อยกเว้น (ซึ่งแนะนำ)

Karl Seguinมีการเขียนที่ยอดเยี่ยมเกี่ยวกับการจัดการข้อยกเว้นในรากฐานของการเขียนโปรแกรม e-bookเช่นกันซึ่งเป็นการอ่านที่ยอดเยี่ยม

แก้ไข: การเชื่อมโยงการทำงานเพื่อฐานรากของการเขียนโปรแกรมแบบ pdf เพียงค้นหาข้อความเพื่อ "ยกเว้น"


10
ฉันไม่แน่ใจว่าถ้าการเขียนที่ยอดเยี่ยมแนะนำให้ลอง {/ ... } catch (Exception ex) {ส่งข้อยกเว้นใหม่ (ex.Message + "สิ่งอื่น ๆ "); } ดี. ปัญหาคือว่าคุณไม่สมบูรณ์ในการจัดการข้อยกเว้นใด ๆ เพิ่มเติมที่กองเว้นแต่คุณจะจับข้อยกเว้นทุกใหญ่ไม่มีไม่มี (คุณแน่ใจว่าต้องการที่จะจัดการกับ OutOfMemoryException ว่า?)
LJS

2
@ljs มีบทความการเปลี่ยนแปลงตั้งแต่ความคิดเห็นของคุณเป็นฉันไม่เห็นส่วนใดที่เขาแนะนำว่า ในทางตรงกันข้ามเขาบอกว่าจะไม่ทำและถามว่าคุณต้องการจัดการ OutOfMemoryException ด้วยหรือไม่!
RyanfaeScotland

6
บางครั้งโยน; ไม่เพียงพอที่จะรักษาสแต็คร่องรอย นี่คือตัวอย่างhttps://dotnetfiddle.net/CkMFoX
Artavazd Balayan

4
หรือExceptionDispatchInfo.Capture(ex).Throw(); throw;ใน. NET +4.5 stackoverflow.com/questions/57383/…
อัลเฟรดวอลเลซ

@ AlfredWallace แก้ปัญหาทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน ลอง {... } catch {throw} ไม่ได้เก็บการติดตามสแต็ก ขอบคุณ.
atownson

100

ถ้าคุณโยนข้อยกเว้นใหม่ที่มีข้อยกเว้นครั้งแรกที่คุณจะรักษากองติดตามเริ่มต้นมากเกินไป ..

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

ไม่ว่าฉันจะลองโยน Exception ใหม่ ("message", ex) ซ้ำจะส่ง ex ex และไม่สนใจข้อความที่กำหนดเอง โยนข้อยกเว้นใหม่ ( "ข้อความ" ex.InnerException) ทำงานแม้ว่า
ท็อด

หากไม่มีข้อยกเว้นที่กำหนดเองเราสามารถใช้ AggregateException (.NET 4+) msdn.microsoft.com/en-us/library/ …
Nikos

AggregateExceptionควรใช้สำหรับข้อยกเว้นเกี่ยวกับการดำเนินการรวมเท่านั้น ตัวอย่างเช่นมันจะถูกโยนโดยParallelEnumerableและTaskคลาสของ CLR การใช้งานควรเป็นไปตามตัวอย่างนี้
Aluan Haddad

29

จริงๆแล้วมีบางสถานการณ์ที่throwstatment จะไม่เก็บข้อมูล StackTrace ตัวอย่างเช่นในรหัสด้านล่าง:

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

StackTrace จะระบุว่าบรรทัด 54 ยกข้อยกเว้นถึงแม้ว่าจะถูกยกที่บรรทัดที่ 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

ในสถานการณ์เช่นเดียวกับที่อธิบายไว้ข้างต้นมีสองตัวเลือกในการ preseve StackTrace ดั้งเดิม:

เรียก Exception.InternalPreserveStackTrace

เนื่องจากเป็นวิธีส่วนตัวจึงต้องเรียกใช้โดยใช้การสะท้อนภาพ:

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

ผมมีข้อเสียของการอาศัยวิธีการส่วนตัวเพื่อรักษาข้อมูล StackTrace ที่ สามารถเปลี่ยนแปลงได้ใน. NET Framework รุ่นอนาคต ตัวอย่างรหัสข้างต้นและโซลูชั่นที่นำเสนอดังต่อไปนี้ถูกสกัดจากFabrice MARGUERIE เว็บบล็อก

การเรียกข้อยกเว้น SetObjectData

เทคนิคด้านล่างนี้ได้รับการแนะนำโดยAnton Tykhyyเป็นคำตอบสำหรับใน C # ฉันจะสร้าง InnerException ใหม่ได้อย่างไรโดยไม่สูญเสียคำถามการติดตามสแต็

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 
} 

แม้ว่าจะมีข้อได้เปรียบของการใช้วิธีการสาธารณะเท่านั้น แต่ยังขึ้นอยู่กับตัวสร้างข้อยกเว้นต่อไปนี้ (ซึ่งข้อยกเว้นบางอย่างที่พัฒนาโดยบุคคลที่สามไม่ได้ใช้):

protected Exception(
    SerializationInfo info,
    StreamingContext context
)

ในสถานการณ์ของฉันฉันจะต้องเลือกวิธีแรกเพราะข้อยกเว้นที่เกิดขึ้นโดยห้องสมุดของบุคคลที่ 3 ผมใช้ไม่ได้ใช้คอนสตรัคนี้


1
คุณสามารถจับข้อยกเว้นและเผยแพร่ได้ทุกข้อยกเว้นนี้คุณต้องการ จากนั้นโยนอันใหม่เพื่ออธิบายสิ่งที่เกิดขึ้นกับผู้ใช้ ด้วยวิธีนี้คุณจะเห็นสิ่งที่เกิดขึ้นในเวลาปัจจุบันที่มีการยกเว้นผู้ใช้ไม่สนใจสิ่งที่เกิดขึ้นจริง
Çöđěxěŕ

2
ด้วย. NET 4.5 มีตัวเลือกที่สามและในความคิดของฉัน - สะอาดกว่า: ใช้ ExceptionDispatchInfo ดูคำตอบ tragedians กับคำถามที่เกี่ยวข้องที่นี่: stackoverflow.com/a/17091351/567000สำหรับข้อมูลเพิ่มเติม
Søren Boisen

20

เมื่อคุณthrow exคุณเป็นหลักโยนข้อยกเว้นใหม่และจะพลาดในข้อมูลสแต็คร่องรอยเดิม throwเป็นวิธีที่ต้องการ


13

กฎง่ายๆคือการหลีกเลี่ยงการจับและโยนExceptionวัตถุพื้นฐาน สิ่งนี้บังคับให้คุณฉลาดขึ้นเล็กน้อยเกี่ยวกับข้อยกเว้น ในคำอื่น ๆ ที่คุณควรจะมีการจับอย่างชัดเจนสำหรับเพื่อให้การจัดการรหัสของคุณไม่ได้ทำอะไรผิดด้วยSqlExceptionNullReferenceException

ในโลกแห่งความจริง แต่การจับและการเข้าสู่ระบบยกเว้นฐานยังเป็นวิธีที่ดี แต่ไม่ลืมที่จะเดินเรื่องทั้งหมดจะได้รับการใด ๆInnerExceptionsก็อาจจะมี


2
ฉันคิดว่ามันเป็นที่ดีที่สุดในการจัดการกับข้อยกเว้นที่ไม่สามารถจัดการเพื่อวัตถุประสงค์ในการเข้าสู่ระบบโดยใช้ข้อยกเว้น AppDomain.CurrentDomain.UnhandledException และ Application.ThreadException ใช้ลองใหญ่ { ... } catch (Exception อดีต) { ... } บล็อกทุกความหมายอย่างมากของการทำสำเนา ขึ้นอยู่กับว่าคุณต้องการบันทึกข้อยกเว้นที่จัดการหรือไม่ซึ่งในกรณีนี้ (อย่างน้อยที่สุด) การทำซ้ำอาจหลีกเลี่ยงไม่ได้
ljs

บวกกับการใช้กิจกรรมเหล่านั้นหมายความว่าคุณ ทำเข้าสู่ระบบจัดการข้อยกเว้นทั้งหมดในขณะที่ถ้าคุณใช้ OL ใหญ่' ลอง { ... } catch (Exception อดีต) { ... } บล็อกคุณอาจจะพลาดบางอย่าง
ljs

10

คุณควรใช้ "throw;" การสร้างข้อยกเว้นใน. NET อีกครั้ง

แนะนำสิ่งนี้ http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

โดยทั่วไป MSIL (CIL) มีสองคำแนะนำ - "โยน" และ "rethrow":

  • C # ของ "โยนอดีต;" ได้รับการรวบรวมเป็น "โยน" ของ MSIL
  • "การโยน" ของ C # - เข้าสู่ MSIL "รื้อถอน"!

โดยทั่วไปฉันสามารถเห็นเหตุผลที่ว่า "Throw อดีต" แทนที่การติดตามสแต็ก


ลิงค์ - จริง ๆ แล้วแหล่งที่มาของลิงค์อ้างอิงนั้นเต็มไปด้วยข้อมูลที่ดีและยังบันทึกผู้ร้ายที่เป็นไปได้ว่าทำไมหลายคนคิดว่าthrow ex;จะรื้อฟื้น - ใน Java มันทำ! แต่คุณควรรวมข้อมูลนั้นไว้ที่นี่เพื่อให้ได้คำตอบเกรด A (แม้ว่าฉันจะยังคงติดตามExceptionDispatchInfo.Captureคำตอบจาก jeuoekdcwzfwccu .)
ruffin

10

ไม่มีใครอธิบายความแตกต่างระหว่างExceptionDispatchInfo.Capture( ex ).Throw()และธรรมดาthrowดังนั้นนี่คือ throwแต่บางคนได้สังเกตเห็นปัญหาที่เกิดขึ้นกับ

วิธีที่สมบูรณ์ในการสร้างข้อยกเว้นที่ถูกดักจับใหม่อีกครั้งคือการใช้ExceptionDispatchInfo.Capture( ex ).Throw()(ใช้ได้เฉพาะจาก. Net 4.5)

ด้านล่างมีกรณีที่จำเป็นในการทดสอบนี้:

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

กรณีที่ 1 และกรณีที่ 2 จะให้การติดตามสแต็กแก่คุณโดยที่หมายเลขบรรทัดซอร์สโค้ดสำหรับCallingMethodวิธีคือหมายเลขบรรทัดของthrow new Exception( "TEST" )บรรทัด

อย่างไรก็ตามกรณีที่ 3 จะให้การติดตามสแต็กแก่คุณโดยที่หมายเลขบรรทัดซอร์สโค้ดสำหรับCallingMethodวิธีคือหมายเลขบรรทัดของการthrowโทร ซึ่งหมายความว่าหากthrow new Exception( "TEST" )บรรทัดถูกล้อมรอบด้วยการดำเนินการอื่นคุณไม่มีความคิดว่าหมายเลขบรรทัดใดที่มีข้อยกเว้นเกิดขึ้นจริง

กรณีที่ 4 จะคล้ายกับกรณีที่ 2 เพราะจำนวนบรรทัดของข้อยกเว้นเดิมจะถูกรักษาไว้ แต่ไม่ได้เป็น rethrow จริงเพราะการเปลี่ยนแปลงประเภทของข้อยกเว้นเดิม


เพิ่มประกาศแจ้งเรื่องง่ายที่จะไม่เคยใช้throw ex;และนี่คือคำตอบที่ดีที่สุดของพวกเขาทั้งหมด
NH

8

มีบางคนที่พลาดจุดสำคัญมาก - 'โยน' และ 'โยนอดีต' อาจทำสิ่งเดียวกัน แต่พวกเขาไม่ให้ชิ้นส่วนที่สำคัญของภาพซึ่งเป็นเส้นที่เกิดข้อยกเว้น

พิจารณารหัสต่อไปนี้:

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

เมื่อคุณทำ 'throw' หรือ 'throw ex' คุณจะได้รับ stack trace แต่บรรทัด # จะเป็น # 22 ดังนั้นคุณไม่สามารถหาได้ว่าบรรทัดใดที่กำลังทำการยกเว้น (เว้นแต่คุณมีเพียง 1 หรือไม่กี่ตัวเท่านั้น บรรทัดของรหัสในบล็อกลอง) ในการรับบรรทัดที่ # 17 ที่คาดไว้ในข้อยกเว้นของคุณคุณจะต้องโยนข้อยกเว้นใหม่ด้วยการติดตามสแต็กข้อยกเว้นเดิม


3

คุณอาจใช้:

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

และข้อยกเว้นใด ๆ ที่โยนจะทำให้เกิดฟองขึ้นไปอีกระดับที่จัดการพวกเขา


3

ฉันจะใช้อย่างแน่นอน:

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

ที่จะรักษาสแต็คของคุณ


1
เพื่อความยุติธรรมที่จะผ่านมาฉันในปี 2008 OP ได้ขอวิธีการรักษากอง - และ 2008 ฉันให้คำตอบที่ถูกต้อง สิ่งที่ขาดหายไปจากคำตอบของฉันคือส่วนหนึ่งของการทำสิ่งที่จับได้จริง
1kevgriff

@JohnSaunders นั่นคือความจริงและถ้าหากคุณไม่ได้ทำอะไรก่อนthrow; ตัวอย่างเช่นคุณสามารถล้างข้อมูลทิ้งได้
Meirion Hughes

@ meirion เมื่อฉันเขียนความคิดเห็นไม่มีอะไรก่อนโยน เมื่อเพิ่มแล้วฉันก็ลงคะแนน แต่ไม่ได้ลบความคิดเห็น
John Saunders

0

FYI ฉันเพิ่งทดสอบสิ่งนี้และการติดตามสแต็กที่รายงานโดย 'throw;' ไม่ใช่การติดตามสแต็กที่ถูกต้องทั้งหมด ตัวอย่าง:

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

สแต็กการติดตามชี้ไปที่จุดเริ่มต้นของข้อยกเว้นอย่างถูกต้อง (หมายเลขบรรทัดที่รายงาน) แต่หมายเลขบรรทัดที่รายงานสำหรับ foo () คือบรรทัดของการโยน คำสั่งดังนั้นคุณไม่สามารถบอกได้ว่าการเรียกไปที่ bar () ใดที่ทำให้เกิดข้อยกเว้น


ซึ่งเป็นเหตุผลที่ดีที่สุดที่จะไม่พยายามที่จะจับข้อยกเว้นถ้าคุณวางแผนที่จะทำอะไรกับพวกเขา
เนท Zaugg
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.