มีความแตกต่างระหว่าง "โยน" และ "โยนอดีต" หรือไม่?


437

มีบางโพสต์ที่ถามว่าความแตกต่างระหว่างสองโพสต์นั้นมีอะไรบ้าง
(ทำไมฉันต้องพูดถึงเรื่องนี้ด้วย ... )

แต่คำถามของฉันแตกต่างกันในวิธีที่ฉันเรียกว่า "throw ex" ในวิธีการจัดการข้อผิดพลาดเหมือนพระเจ้า

public class Program {
    public static void Main(string[] args) {
        try {
            // something
        } catch (Exception ex) {
            HandleException(ex);
        }
    }

    private static void HandleException(Exception ex) {
        if (ex is ThreadAbortException) {
            // ignore then,
            return;
        }
        if (ex is ArgumentOutOfRangeException) { 
            // Log then,
            throw ex;
        }
        if (ex is InvalidOperationException) {
            // Show message then,
            throw ex;
        }
        // and so on.
    }
}

หากtry & catchมีการใช้ในการMainแล้วฉันจะใช้throw;เพื่อรื้อฟื้นข้อผิดพลาด แต่ในโค้ดด้านบนอย่างง่ายข้อยกเว้นทั้งหมดจะผ่านไปHandleException

ไม่throw ex;ได้มีผลเช่นเดียวกับการเรียกthrowเมื่อเรียกภายในHandleException?


3
มีความแตกต่างมันเกี่ยวข้องกับว่าร่องรอยสแต็กปรากฏในข้อยกเว้นหรือไม่ แต่ฉันจำไม่ได้ว่าอันไหนอยู่ตอนนี้ดังนั้นฉันจะไม่แสดงคำตอบนี้
Joel Coehoorn

@ Joel: ขอบคุณ ฉันเดาว่าการใช้ข้อยกเว้น HandleError เป็นความคิดที่ไม่ดี ฉันแค่ต้องการ refactor รหัสการจัดการข้อผิดพลาด
dance2die

1
วิธีที่สามคือการห่อยกเว้นใหม่และ rethrow timwise.blogspot.co.uk/2014/05/…
Tim Abell

คำตอบ:


679

ใช่มีความแตกต่าง;

  • throw exรีเซ็ตการติดตามสแต็ก (ดังนั้นข้อผิดพลาดของคุณจะดูเหมือนมาจากHandleException)
  • throw ไม่ - ผู้กระทำผิดดั้งเดิมจะได้รับการเก็บรักษาไว้

    static void Main(string[] args)
    {
        try
        {
            Method2();
        }
        catch (Exception ex)
        {
            Console.Write(ex.StackTrace.ToString());
            Console.ReadKey();
        }
    }
    
    private static void Method2()
    {
        try
        {
            Method1();
        }
        catch (Exception ex)
        {
            //throw ex resets the stack trace Coming from Method 1 and propogates it to the caller(Main)
            throw ex;
        }
    }
    
    private static void Method1()
    {
        try
        {
            throw new Exception("Inside Method1");
        }
        catch (Exception)
        {
            throw;
        }
    }

28
หากต้องการขยายคำตอบของ Marc คุณสามารถหารายละเอียดเพิ่มเติมได้ที่นี่: geekswithblogs.net/sdorman/archive/2007/08/20/…
Scott Dorman

3
@Shaul; ไม่มันไม่ใช่ ฉันให้รายละเอียดในความคิดเห็นในโพสต์ของคุณ
Marc Gravell

1
@ Marc Gravell - คำขอโทษของฉันคุณพูดถูก ขออภัยเกี่ยวกับ downvote; ที่จะสายเกินไปสำหรับผมที่จะยกเลิก ... :(
ชาอู Behr

3
@ Marc: ดูเหมือนว่าการขว้างเก็บไว้ซึ่งผู้กระทำความผิดเดิมเท่านั้นหากการโยนไม่ได้อยู่ในวิธีการที่มีข้อยกเว้นเริ่มต้นถูกโยน (ดูคำถามนี้: stackoverflow.com/questions/5152265/… )
Brann

3
@ScottDorman ดูเหมือนว่าลิงก์ของคุณจะไม่ถูกส่งต่ออย่างถูกต้องหลังจากการย้ายบล็อก ดูเหมือนว่าตอนนี้อาศัยอยู่ที่นี่ แก้ไข:เดี๋ยวก่อนนั่นคือบล็อกของคุณ ! แก้ไขลิงก์ของคุณเอง! ; ^ D
ruffin

96

(ฉันโพสต์ก่อนหน้านี้และ @Marc Gravell แก้ไขฉันแล้ว)

นี่คือตัวอย่างของความแตกต่าง:

static void Main(string[] args) {
    try {
        ThrowException1(); // line 19
    } catch (Exception x) {
        Console.WriteLine("Exception 1:");
        Console.WriteLine(x.StackTrace);
    }
    try {
        ThrowException2(); // line 25
    } catch (Exception x) {
        Console.WriteLine("Exception 2:");
        Console.WriteLine(x.StackTrace);
    }
}

private static void ThrowException1() {
    try {
        DivByZero(); // line 34
    } catch {
        throw; // line 36
    }
}
private static void ThrowException2() {
    try {
        DivByZero(); // line 41
    } catch (Exception ex) {
        throw ex; // line 43
    }
}

private static void DivByZero() {
    int x = 0;
    int y = 1 / x; // line 49
}

และนี่คือผลลัพธ์:

Exception 1:
   at UnitTester.Program.DivByZero() in <snip>\Dev\UnitTester\Program.cs:line 49
   at UnitTester.Program.ThrowException1() in <snip>\Dev\UnitTester\Program.cs:line 36
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 19

Exception 2:
   at UnitTester.Program.ThrowException2() in <snip>\Dev\UnitTester\Program.cs:line 43
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 25

คุณสามารถเห็นได้ว่าในข้อยกเว้น 1 การติดตามสแต็กจะกลับไปที่DivByZero()วิธีการในขณะที่ข้อยกเว้น 2 ไม่ได้

อย่างไรก็ตามให้สังเกตว่าหมายเลขบรรทัดที่แสดงในThrowException1()และThrowException2()เป็นหมายเลขบรรทัดของthrowคำสั่งไม่ใช่หมายเลขบรรทัดของการโทรไปDivByZero()ที่ซึ่งอาจสมเหตุสมผลในขณะนี้ที่ฉันคิดเกี่ยวกับมันเล็กน้อย ...

เอาต์พุตในโหมด Release

ข้อยกเว้น 1:

at ConsoleAppBasics.Program.ThrowException1()
at ConsoleAppBasics.Program.Main(String[] args)

ข้อยกเว้น 2:

at ConsoleAppBasics.Program.ThrowException2()
at ConsoleAppBasics.Program.Main(String[] args)

มันจะรักษา stackTrace ดั้งเดิมในโหมดดีบักเท่านั้นหรือไม่


1
มันเป็นเพราะกระบวนการเพิ่มประสิทธิภาพของคอมไพเลอร์ inline วิธีการสั้น ๆ เช่นDevideByZeroดังนั้นการติดตามสแต็คจะเหมือนกัน บางทีคุณควรโพสต์คำถามนี้ด้วยตัวเอง
Menahem

42

คำตอบอื่น ๆ นั้นถูกต้องทั้งหมด แต่คำตอบนี้ให้ส่วนที่เป็นพิเศษเพิ่มเติมฉันคิดว่า

ลองพิจารณาตัวอย่างนี้:

using System;

static class Program {
  static void Main() {
    try {
      ThrowTest();
    } catch (Exception e) {
      Console.WriteLine("Your stack trace:");
      Console.WriteLine(e.StackTrace);
      Console.WriteLine();
      if (e.InnerException == null) {
        Console.WriteLine("No inner exception.");
      } else {
        Console.WriteLine("Stack trace of your inner exception:");
        Console.WriteLine(e.InnerException.StackTrace);
      }
    }
  }

  static void ThrowTest() {
    decimal a = 1m;
    decimal b = 0m;
    try {
      Mult(a, b);  // line 34
      Div(a, b);   // line 35
      Mult(b, a);  // line 36
      Div(b, a);   // line 37
    } catch (ArithmeticException arithExc) {
      Console.WriteLine("Handling a {0}.", arithExc.GetType().Name);

      //   uncomment EITHER
      //throw arithExc;
      //   OR
      //throw;
      //   OR
      //throw new Exception("We handled and wrapped your exception", arithExc);
    }
  }

  static void Mult(decimal x, decimal y) {
    decimal.Multiply(x, y);
  }
  static void Div(decimal x, decimal y) {
    decimal.Divide(x, y);
  }
}

หากคุณไม่ใส่เครื่องหมายในthrow arithExc;บรรทัดเอาท์พุทของคุณคือ:

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 44
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

แน่นอนคุณได้สูญเสียข้อมูลเกี่ยวกับข้อยกเว้นที่เกิดขึ้น หากคุณใช้throw;สายแทนนี่คือสิ่งที่คุณจะได้รับ:

Handling a DivideByZeroException.
Your stack trace:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 46
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

มันดีกว่ามากเพราะตอนนี้คุณเห็นแล้วว่ามันเป็นProgram.Divวิธีการที่ทำให้คุณเกิดปัญหา แต่ก็ยังยากที่จะดูว่าปัญหานี้มาจากบรรทัดที่ 35 หรือบรรทัดที่ 37 ในtryบล็อก

หากคุณใช้ทางเลือกที่สามห่อด้วยข้อยกเว้นภายนอกคุณจะไม่สูญเสียข้อมูลใด ๆ :

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 48
   at Program.Main() in c:\somepath\Program.cs:line 9

Stack trace of your inner exception:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 35

โดยเฉพาะอย่างยิ่งคุณจะเห็นว่ามันเป็นบรรทัดที่ 35ที่นำไปสู่ปัญหา อย่างไรก็ตามสิ่งนี้ต้องการผู้คนในการค้นหาInnerExceptionและรู้สึกค่อนข้างทางอ้อมในการใช้ข้อยกเว้นภายในในกรณีง่าย ๆ

ในการโพสต์บล็อกนี้พวกเขารักษาหมายเลขบรรทัด (สายของลองบล็อก) โดยการเรียก (ผ่านการสะท้อน) internalวิธีการแสดงเจตนาInternalPreserveStackTrace()ในExceptionวัตถุ แต่ก็ไม่ดีที่จะใช้การสะท้อนเช่นนั้น (. NET Framework อาจเปลี่ยนinternalสมาชิกของพวกเขาบางวันโดยไม่มีการเตือน)


6

มาทำความเข้าใจความแตกต่างระหว่างการโยนกับการโยนทิ้ง ฉันได้ยินมาว่าในการสัมภาษณ์. net หลายครั้งที่มีการถามกันนี้

เพียงเพื่อให้ภาพรวมของคำศัพท์ทั้งสองนี้ทั้งการโยนและการโยนจะใช้เพื่อทำความเข้าใจว่าข้อยกเว้นเกิดขึ้นที่ใด Throw อดีตเขียนสแต็กการติดตามใหม่ของข้อยกเว้นโดยไม่คำนึงถึงตำแหน่งที่ถูกโยนทิ้งจริง

มาทำความเข้าใจกับตัวอย่าง

มาทำความเข้าใจกับการโยนครั้งแรก

static void Main(string[] args) {
    try {
        M1();
    } catch (Exception ex) {
        Console.WriteLine(" -----------------Stack Trace Hierarchy -----------------");
        Console.WriteLine(ex.StackTrace.ToString());
        Console.WriteLine(" ---------------- Method Name / Target Site -------------- ");
        Console.WriteLine(ex.TargetSite.ToString());
    }
    Console.ReadKey();
}

static void M1() {
    try {
        M2();
    } catch (Exception ex) {
        throw;
    };
}

static void M2() {
    throw new DivideByZeroException();
}

การส่งออกของด้านบนอยู่ด้านล่าง

แสดงลำดับชั้นที่สมบูรณ์และชื่อเมธอดโดยที่ข้อยกเว้นเกิดขึ้นจริง .. คือ M2 -> M2 พร้อมกับหมายเลขบรรทัด

ป้อนคำอธิบายรูปภาพที่นี่

ประการที่สอง .. ช่วยให้เข้าใจโดยการส่งอดีต เพียงแค่แทนที่ Throw ด้วย Throw ex ใน M2 catch catch block ดังต่อไปนี้.

ป้อนคำอธิบายรูปภาพที่นี่

ผลลัพธ์ของการโยนรหัส ex ดังต่อไปนี้ ..

ป้อนคำอธิบายรูปภาพที่นี่

คุณสามารถเห็นความแตกต่างในเอาต์พุต .. throw ex เพียงแค่ละเว้นลำดับชั้นก่อนหน้าทั้งหมดและรีเซ็ตการติดตามสแต็กด้วยบรรทัด / วิธีที่การเขียน throw ex


5

เมื่อคุณทำ throw exนั้นข้อยกเว้นที่ส่งออกจะกลายเป็น "ต้นฉบับ" ดังนั้นการติดตามสแต็กก่อนหน้าทั้งหมดจะไม่อยู่ที่นั่น

ถ้าคุณทำthrowข้อยกเว้นจะลงไปที่บรรทัดและคุณจะได้รับการติดตามสแต็กเต็ม


4

ไม่สิ่งนี้จะทำให้เกิดข้อยกเว้นเพื่อให้มีการติดตามสแต็กที่แตกต่างกัน ใช้เฉพาะthrowวัตถุที่ไม่มีข้อยกเว้นในcatchตัวจัดการเท่านั้นจะทำให้การติดตามสแต็กไม่เปลี่ยนแปลง

คุณอาจต้องการส่งคืนบูลีนจาก HandleException ไม่ว่าจะมีข้อยกเว้นเกิดขึ้นซ้ำหรือไม่


4

MSDN หมายถึง :

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


2

ดูที่นี่: http://blog-mstechnology.blogspot.de/2010/06/throw-vs-throw-ex.html

โยน :

try 
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw;
}

มันรักษาข้อมูลกองด้วยข้อยกเว้น

สิ่งนี้เรียกว่า "Rethrow"

ถ้าต้องการโยนข้อยกเว้นใหม่

throw new ApplicationException("operation failed!");

โยนอดีต :

try
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw ex;
}

มันจะไม่ส่งข้อมูลสแต็กด้วยข้อยกเว้น

สิ่งนี้เรียกว่า "การทำลายกอง"

ถ้าต้องการโยนข้อยกเว้นใหม่

throw new ApplicationException("operation failed!",ex);

0

เพื่อให้มุมมองที่แตกต่างกับคุณการใช้ Throw มีประโยชน์อย่างยิ่งหากคุณมอบ API ให้กับลูกค้าและคุณต้องการให้ข้อมูลการติดตามสแต็กแบบละเอียดสำหรับไลบรารีภายในของคุณ โดยใช้การโยนที่นี่ฉันจะได้รับการติดตามสแต็คในกรณีของห้องสมุด System.IO.File นี้สำหรับ File.Delete ถ้าฉันใช้ throw ex ข้อมูลนั้นจะไม่ถูกส่งไปยังตัวจัดการของฉัน

static void Main(string[] args) {            
   Method1();            
}

static void Method1() {
    try {
        Method2();
    } catch (Exception ex) {
        Console.WriteLine("Exception in Method1");             
    }
}

static void Method2() {
    try {
        Method3();
    } catch (Exception ex) {
        Console.WriteLine("Exception in Method2");
        Console.WriteLine(ex.TargetSite);
        Console.WriteLine(ex.StackTrace);
        Console.WriteLine(ex.GetType().ToString());
    }
}

static void Method3() {
    Method4();
}

static void Method4() {
    try {
        System.IO.File.Delete("");
    } catch (Exception ex) {
        // Displays entire stack trace into the .NET 
        // or custom library to Method2() where exception handled
        // If you want to be able to get the most verbose stack trace
        // into the internals of the library you're calling
        throw;                
        // throw ex;
        // Display the stack trace from Method4() to Method2() where exception handled
    }
}

-1
int a = 0;
try {
    int x = 4;
    int y ;
    try {
        y = x / a;
    } catch (Exception e) {
        Console.WriteLine("inner ex");
        //throw;   // Line 1
        //throw e;   // Line 2
        //throw new Exception("devide by 0");  // Line 3
    }
} catch (Exception ex) {
    Console.WriteLine(ex);
    throw ex;
}
  1. ถ้าทุกบรรทัด 1, 2 และ 3 ถูกคอมเม้นท์ - เอาท์พุท - Inner ex

  2. ถ้าทุกบรรทัดที่ 2 และ 3 ถูกคอมเม้นท์ - เอาท์พุท - Inner ex System.DevideByZeroException: {"พยายามหารด้วยศูนย์"} ---------

  3. ถ้าทุกบรรทัด 1 และ 2 ถูกคอมเม้นท์ - เอาท์พุท - Inner ex System.Exception: devide by 0 ----

  4. ถ้าทุกบรรทัดที่ 1 และ 3 ถูกคอมเม้นท์ - เอาท์พุท - Inner ex System.DevideByZeroException: {"พยายามหารด้วยศูนย์"} ---------

และ StackTrace จะถูกรีเซ็ตในกรณีที่มีการทุ่มจากอดีต

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