ความแตกต่างระหว่างการโยนและการโยนข้อยกเว้นใหม่ ()


164

อะไรคือความแตกต่างระหว่าง

try { ... }
catch{ throw } 

และ

try{ ... }
catch(Exception e) {throw new Exception(e.message) } 

ไม่ว่าข้อความที่สองจะแสดงข้อความหรือไม่?


51
ตัวอย่างที่สองเป็นหนึ่งในโค้ดที่ชั่วร้ายที่สุด (แต่ไม่มีพิษภัย) ที่ฉันเคยเห็น
SLAK

คำตอบ:


259

throw; rethrows ข้อยกเว้นเดิมและเก็บสแต็กการติดตามดั้งเดิม

throw ex;ส่งข้อยกเว้นดั้งเดิม แต่รีเซ็ตการติดตามสแต็กทำลายข้อมูลการติดตามสแต็กทั้งหมดจนกว่าcatchบล็อกของคุณ


ไม่เคยเขียนthrow ex;


throw new Exception(ex.Message);ยิ่งเลวร้ายลง มันสร้างExceptionอินสแตนซ์ใหม่เอี่ยมสูญเสียการติดตามสแต็กดั้งเดิมของข้อยกเว้นรวมถึงชนิดของมัน (เช่น, IOException)
นอกจากนี้ข้อยกเว้นบางอย่างมีข้อมูลเพิ่มเติม (เช่นArgumentException.ParamName)
throw new Exception(ex.Message); จะทำลายข้อมูลนี้ด้วย

ในบางกรณีคุณอาจต้องการรวมข้อยกเว้นทั้งหมดในวัตถุข้อยกเว้นที่กำหนดเองเพื่อให้คุณสามารถให้ข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่รหัสกำลังทำอยู่เมื่อมีการโยนข้อยกเว้น

การทำเช่นนี้กำหนดคลาสใหม่ที่สืบทอดException, เพิ่มทั้งสี่ก่อสร้างยกเว้นและเลือกนวกรรมิกเพิ่มเติมที่จะใช้เวลาInnerExceptionเช่นเดียวกับการเพิ่มเติมข้อมูลและโยนระดับยกเว้นใหม่ของคุณผ่านexเป็นInnerExceptionพารามิเตอร์ โดยการส่งต้นฉบับInnerExceptionคุณรักษาคุณสมบัติของข้อยกเว้นดั้งเดิมทั้งหมดรวมถึงการติดตามสแต็ก


24
"โยนข้อยกเว้นใหม่ (ex); ยิ่งแย่ลงกว่าเดิม": ฉันไม่เห็นด้วยกับเรื่องนี้ บางครั้งคุณต้องการเปลี่ยนประเภทของข้อยกเว้นแล้วเก็บข้อยกเว้นเดิมไว้เป็นข้อยกเว้นภายในจะดีที่สุดที่คุณสามารถทำได้ แม้ว่ามันควรจะเป็นthrow new MyCustomException(myMessage, ex);ของหลักสูตร
Dirk Vollmar

9
@ 0xA3: ผมหมายex.Messageซึ่งเป็นที่เลวร้ายยิ่ง
SLAK

6
นอกเหนือจากการใช้งานตัวสร้างมาตรฐานแล้วเราควรสร้างข้อยกเว้นแบบกำหนดเอง[Serializable()]ด้วย
Dirk Vollmar

21
ยอว์อว์, เราต้อนฝูงสัตว์คุณชอบข้อยกเว้นดังนั้นเราจึงวางข้อยกเว้นไว้ในข้อยกเว้นของคุณเพื่อให้คุณสามารถจับได้ในขณะที่คุณจับ
Darth Continent

2
@SLaks: เมื่อคุณหมายเลขบรรทัดที่เกิดขึ้นจริงที่ยกเว้นที่เกิดขึ้นจะถูกแทนที่ด้วยจำนวนบรรทัดของthrow; throw;คุณแนะนำให้จัดการกับสิ่งนั้นอย่างไร stackoverflow.com/questions/2493779/…
Eric J.

34

วิธีแรกคือรักษา stacktrace ดั้งเดิม:

try { ... }
catch
{
    // Do something.
    throw;
}

ที่สองช่วยให้คุณสามารถเปลี่ยนประเภทของข้อยกเว้นและ / หรือข้อความและข้อมูลอื่น ๆ :

try { ... } catch (Exception e)
{
    throw new BarException("Something broke!");
}

นอกจากนี้ยังมีวิธีที่สามที่คุณผ่านข้อยกเว้นภายใน:

try { ... }
catch (FooException e) {
    throw new BarException("foo", e);
} 

ฉันขอแนะนำให้ใช้:

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

6

อีกประเด็นหนึ่งที่ฉันไม่เห็นใครทำ:

หากคุณไม่ทำอะไรเลยในบล็อก catch {} การลอง ... catch ไม่มีจุดหมาย ฉันเห็นสิ่งนี้ตลอดเวลา:

try 
{
  //Code here
}
catch
{
    throw;
}

หรือแย่กว่านั้น:

try 
{
  //Code here
}
catch(Exception ex)
{
    throw ex;
}

แย่ที่สุด:

try 
{
  //Code here
}
catch(Exception ex)
{
    throw new System.Exception(ex.Message);
}

ฉันเห็นด้วยเว้นแต่คุณจะมีข้อสุดท้าย
Toni Rossmann

1
@ToniRossmann ในกรณีนี้ฉันจะใช้ลอง .. ในที่สุดโดยไม่มีการจับเว้นแต่คุณจะทำสิ่งอื่นนอกเหนือจากการโยน;
JLWarlow

4

throwโยนข้อยกเว้นที่ตรวจพบอีกครั้งกลับไปที่การติดตามสแต็กในขณะที่throw new Exceptionสูญเสียรายละเอียดบางส่วนของข้อยกเว้นที่ตรวจพบ

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

BlackWasp ได้เป็นบทความที่ดีพอบรรดาศักดิ์ข้อยกเว้นการขว้างปาใน C #


4

การขว้างข้อยกเว้นใหม่จะทำให้การติดตามสแต็กปัจจุบันหายไป

throw;จะเก็บสแต็กการติดตามดั้งเดิมไว้และจะมีประโยชน์มากกว่า ข้อยกเว้นของกฎนั้นคือเมื่อคุณต้องการสรุปข้อยกเว้นในข้อยกเว้นที่กำหนดเองของคุณเอง จากนั้นคุณควรทำ:

catch(Exception e)
{
    throw new CustomException(customMessage, e);
}

3

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

การใช้throwโดยไม่มีข้อโต้แย้งใด ๆ รักษาสแต็คการโทรสำหรับการดีบัก


0

หากคุณต้องการคุณสามารถโยนข้อยกเว้นใหม่โดยชุดเดิมเป็นข้อยกเว้นภายใน


0

ตัวอย่างที่สองของคุณจะรีเซ็ตการติดตามสแต็กของข้อยกเว้น ครั้งแรกที่ถูกต้องที่สุดรักษาต้นกำเนิดของข้อยกเว้น นอกจากนี้คุณยังได้แกะแบบดั้งเดิมซึ่งเป็นกุญแจสำคัญในการรู้ว่าอะไรผิดพลาดจริง ๆ ... ถ้าจำเป็นต้องใช้ฟังก์ชันที่สองเช่นเพิ่มข้อมูลเพิ่มเติมหรือห่อใหม่ด้วยประเภทพิเศษเช่นกำหนดเอง 'HandleableException' ตรวจสอบให้แน่ใจว่ามีการตั้งค่าคุณสมบัติ InnerException ด้วย!


ใช่นี่เป็นหนึ่งในคำถามที่คุณต้องเขียนเร็ว ;)
Robert Harvey

0

ความแตกต่างที่สำคัญที่สุดคือนิพจน์ที่สองลบประเภทข้อยกเว้น และประเภทการยกเว้นมีบทบาทสำคัญในการตรวจจับข้อยกเว้น:

public void MyMethod ()
{
    // both can throw IOException
    try { foo(); } catch { throw; }
    try { bar(); } catch(E) {throw new Exception(E.message); }
}

(...)

try {
    MyMethod ();
} catch (IOException ex) {
    Console.WriteLine ("Error with I/O"); // [1]
} catch (Exception ex) {
    Console.WriteLine ("Other error");    // [2]
}

ถ้าfoo()โยนIOException, [1]จับบล็อกจะจับข้อยกเว้น แต่เมื่อbar()โยนIOExceptionมันจะถูกแปลงเป็นExceptionมดธรรมดาจะไม่ถูกจับโดย[1]บล็อกจับ


0

โยนหรือโยนอดีตทั้งสองใช้ในการโยนหรือ rethrow ข้อยกเว้นเมื่อคุณเพียงแค่บันทึกข้อมูลข้อผิดพลาดและไม่ต้องการส่งข้อมูลใด ๆ กลับไปยังผู้โทรคุณเพียงแค่บันทึกข้อผิดพลาดในการจับและออก แต่ในกรณีที่คุณต้องการส่งข้อมูลที่มีความหมายเกี่ยวกับข้อยกเว้นไปยังผู้โทรที่คุณใช้การโยนหรือการส่งข้อความ ตอนนี้ความแตกต่างระหว่าง Throw and Throw Ex คือ Throw เก็บการติดตามสแต็กและข้อมูลอื่น ๆ แต่การโยน ex สร้างวัตถุข้อยกเว้นใหม่และด้วยเหตุนี้การติดตามสแต็กเดิมจะหายไป ดังนั้นเมื่อเราควรใช้ Throw and Throw e ยังมีบางสถานการณ์ที่คุณอาจต้องการสร้างข้อยกเว้นใหม่เพื่อรีเซ็ตข้อมูลสแต็กการโทร ตัวอย่างเช่นหากวิธีอยู่ในไลบรารีและคุณต้องการซ่อนรายละเอียดของไลบรารีจากรหัสการโทร คุณไม่ต้องการให้ call stack รวมข้อมูลเกี่ยวกับเมธอดส่วนตัวภายในไลบรารี ในกรณีดังกล่าวคุณสามารถตรวจจับข้อยกเว้นในวิธีการสาธารณะของไลบรารีและจากนั้นทำการสร้างใหม่เพื่อให้การเรียกสแต็คเริ่มต้นด้วยวิธีสาธารณะ


0

โยน; จัดเรียงข้อยกเว้นดั้งเดิมใหม่และเก็บประเภทข้อยกเว้น

โยนข้อยกเว้นใหม่ (); จัดเรียงชนิดข้อยกเว้นดั้งเดิมใหม่และรีเซ็ตการติดตามสแต็กข้อยกเว้น

โยนอดีต รีเซ็ตการติดตามสแต็กข้อยกเว้นและรีเซ็ตประเภทข้อยกเว้น


-1

ไม่มีคำตอบที่นี่แสดงความแตกต่างซึ่งอาจเป็นประโยชน์สำหรับคนที่พยายามเข้าใจความแตกต่าง พิจารณารหัสตัวอย่างนี้:

using System;
using System.Collections.Generic;

namespace ExceptionDemo
{
   class Program
   {
      static void Main(string[] args)
      {
         void fail()
         {
            (null as string).Trim();
         }

         void bareThrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw;
            }
         }

         void rethrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw e;
            }
         }

         void innerThrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw new Exception("outer", e);
            }
         }

         var cases = new Dictionary<string, Action>()
         {
            { "Bare Throw:", bareThrow },
            { "Rethrow", rethrow },
            { "Inner Throw", innerThrow }
         };

         foreach (var c in cases)
         {
            Console.WriteLine(c.Key);
            Console.WriteLine(new string('-', 40));
            try
            {
               c.Value();
            } catch (Exception e)
            {
               Console.WriteLine(e.ToString());
            }
         }
      }
   }
}

ซึ่งสร้างผลลัพธ์ต่อไปนี้:

Bare Throw:
----------------------------------------
System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<Main>g__fail|0_0() in C:\...\ExceptionDemo\Program.cs:line 12
   at ExceptionDemo.Program.<>c.<Main>g__bareThrow|0_1() in C:\...\ExceptionDemo\Program.cs:line 19
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

Rethrow
----------------------------------------
System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<>c.<Main>g__rethrow|0_2() in C:\...\ExceptionDemo\Program.cs:line 35
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

Inner Throw
----------------------------------------
System.Exception: outer ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<Main>g__fail|0_0() in C:\...\ExceptionDemo\Program.cs:line 12
   at ExceptionDemo.Program.<>c.<Main>g__innerThrow|0_3() in C:\...\ExceptionDemo\Program.cs:line 43
   --- End of inner exception stack trace ---
   at ExceptionDemo.Program.<>c.<Main>g__innerThrow|0_3() in C:\...\ExceptionDemo\Program.cs:line 47
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

การโยนเปล่าตามที่ระบุไว้ในคำตอบก่อนหน้านี้แสดงให้เห็นอย่างชัดเจนทั้งบรรทัดต้นฉบับของรหัสที่ล้มเหลว (บรรทัดที่ 12) รวมถึงจุดอีกสองจุดที่ใช้งานในการโทรสแต็กเมื่อเกิดข้อยกเว้น (บรรทัดที่ 19 และ 64)

ผลลัพธ์ของเคสตัวใหม่จะแสดงสาเหตุที่เป็นปัญหา เมื่อมีการสร้างข้อยกเว้นซ้ำเช่นนี้ข้อยกเว้นจะไม่รวมข้อมูลสแต็กดั้งเดิม โปรดทราบว่าจะรวมเฉพาะจุดthrow e(สาย 35) และจุดสแต็กการโทรด้านนอกสุด (บรรทัด 64) มันจะเป็นการยากที่จะติดตามเมธอด fail () เป็นแหล่งที่มาของปัญหาหากคุณโยนข้อยกเว้นด้วยวิธีนี้

กรณีสุดท้าย (InnerThrow) มีความละเอียดมากที่สุดและรวมถึงข้อมูลมากกว่าด้านใดด้านหนึ่ง เนื่องจากเรากำลังยกข้อยกเว้นใหม่ขึ้นมาเราจึงมีโอกาสเพิ่มข้อมูลบริบท (ข้อความ "outer" ที่นี่ แต่เรายังสามารถเพิ่มพจนานุกรม. Data ในข้อยกเว้นใหม่) รวมถึงการรักษาข้อมูลทั้งหมดในต้นฉบับ ข้อยกเว้น (รวมถึงลิงก์ช่วยเหลือพจนานุกรมข้อมูล ฯลฯ )

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