ในที่สุดจะทำงานเมื่อใดหากคุณทิ้งข้อยกเว้นจากบล็อกจับ?


141
try {
   // Do stuff
}
catch (Exception e) {
   throw;
}
finally {
   // Clean up
}

ในบล็อกด้านบนบล็อกสุดท้ายเรียกเมื่อใด ก่อนโยนอีหรือสุดท้ายเรียกแล้วจับ?


16
ps คุณไม่ควร "โยน e;" เนื่องจากจะทำให้การติดตามสแต็กของข้อยกเว้นเดิมยุ่งเหยิง คุณควร "โยน;" หรือสร้างข้อยกเว้นใหม่และตั้งค่า InnerException เป็น "e" ก่อนที่คุณจะโยนทิ้ง
Erv Walter

24
ในที่สุดก็จะเป็นทางเลือกที่ดีงามของคำหลักถ้ามันไม่ได้ทำงานที่ผ่านมาคุณจะไม่พูด?
Eric Lippert

@ErvWalter นี่ยังจริงอยู่มั้ย? ฉันกำลังทดสอบทั้งสองวิธีใน VS2017 และดูเหมือนว่าจะเหมือนกันทุกประการ คุณสามารถให้ข้อมูลเพิ่มเติมหรือข้อมูลอ้างอิงได้หรือไม่? ขอบคุณ
Jeff Puckett

คำแนะนำในการตั้งชื่อให้ใช้ Exception ex - reserve e for events / delegates
mr R

คำตอบ:


145

มันจะถูกเรียกหลังจาก e ถูกโยนใหม่ (เช่นหลังจากดำเนินการจับบล็อก)

การแก้ไขสิ่งนี้ในอีก 7 ปีต่อมาสิ่งสำคัญประการหนึ่งก็คือหากeบล็อก try / catch ไม่ติดขึ้นจาก call stack หรือจัดการโดยตัวจัดการข้อยกเว้นส่วนกลางfinallyบล็อกอาจไม่ดำเนินการเลย


18
และไม่เคยถ้าคุณเรียก Envrionment.FailFast ()
Johannes Rudolph

17
หลังจากที่พยายามรหัสในคำตอบของแบรนดอนผมเห็นว่าfinallyจะไม่ทำงานถ้ายกเว้นโยนในก่อนหน้านี้catchไม่เคยถูกจับในด้านนอกtry- catchบล็อก!
Andrew

4
ขอขอบคุณสำหรับการแก้ไขคำตอบ (ยอมรับ) ของคุณเพื่อรวมข้อมูลใหม่
Gordon Bean

3
พวกเขา (Microsoft) พูดถึงเรื่องนี้ในไซต์เอกสารใหม่: docs.microsoft.com/en-us/dotnet/csharp/language-reference/… : "ภายในข้อยกเว้นที่ได้รับการจัดการบล็อกสุดท้ายที่เกี่ยวข้องจะรับประกันว่าจะทำงานอย่างไรก็ตาม หากไม่มีการจัดการข้อยกเว้นการดำเนินการของบล็อกในที่สุดจะขึ้นอยู่กับวิธีการเรียกใช้การดำเนินการคลายข้อยกเว้นซึ่งในทางกลับกันจะขึ้นอยู่กับวิธีการตั้งค่าคอมพิวเตอร์ของคุณ "
DotNetSparky

2
โปรดทราบว่า "จับโดยบล็อก try / catch เพิ่มเติมใน call stack" จะรวมตัวจัดการเฟรมเวิร์กเช่น ASP.NET หรือนักวิ่งทดสอบ วิธีที่ดีกว่าในการวางอาจเป็น "ถ้าโปรแกรมของคุณยังคงทำงานต่อไปหลังจากบล็อก catch บล็อกจะทำงานในที่สุด"
ArrowCase

94

ทำไมไม่ลอง:

outer try
inner try
inner catch
inner finally
outer catch
outer finally

ด้วยรหัส (จัดรูปแบบสำหรับพื้นที่แนวตั้ง):

static void Main() {
    try {
        Console.WriteLine("outer try");
        DoIt();
    } catch {
        Console.WriteLine("outer catch");
        // swallow
    } finally {
        Console.WriteLine("outer finally");
    }
}
static void DoIt() {
    try {
        Console.WriteLine("inner try");
        int i = 0;
        Console.WriteLine(12 / i); // oops
    } catch (Exception e) {
        Console.WriteLine("inner catch");
        throw e; // or "throw", or "throw anything"
    } finally {
        Console.WriteLine("inner finally");
    }
}

5
+1 สำหรับบางสิ่งที่เรียบง่ายคุณควรลองเหมือนที่ Marc มี GJ แสดงให้เห็นด้วยการลอง / จับ / ในที่สุดแบบซ้อน :)
Allen Rice

1
@AllenRice ทำไมต้องลองถ้า Marc มีอยู่แล้วและฉันสามารถ google เพื่อหาคำตอบของ Marc ได้? หรืออาจจะดีกว่านั้นลองด้วยตัวเองจากนั้นสร้างคำถาม SO และตอบคำถามด้วยตัวเองเพื่อประโยชน์ของผู้อื่น
joshden

8
โปรดทราบว่าหากคุณไม่จับข้อยกเว้นในการจับด้านนอกสุดท้ายด้านในจะไม่ถูกดำเนินการ !! ในกรณีนี้ผลลัพธ์คือouter try inner try inner catch Unhandled Exception: System.DivideByZeroException...
Andrew

1
@ แอนดริวคุณพูดถูก คุณสามารถดูคำอธิบายได้ที่นี่MSDN Magazine 2008 กันยายน: Unhandled Exception Processing ใน CLR (หากต้องการเปิด chm จำเป็นต้องปลดล็อก: File Properties -> General -> Unlock) หากคุณแทนที่บล็อก catch ด้านนอกด้วย "catch (ArgumentException)" ในที่สุดก็ไม่มีใครบล็อกจะไม่ดำเนินการเช่นกันเนื่องจาก CLR ไม่พบ "ตัวจัดการข้อยกเว้นใด ๆ ที่ตกลงที่จะจัดการข้อยกเว้น" DivideByZeroException
vladimir

37

หลังจากอ่านคำตอบทั้งหมดที่นี่ดูเหมือนว่าคำตอบสุดท้ายจะขึ้นอยู่กับ :

  • หากคุณโยนข้อยกเว้นซ้ำภายในบล็อก catch และข้อยกเว้นนั้นติดอยู่ในบล็อก catch อื่นทุกอย่างจะดำเนินการตามเอกสารประกอบ

  • อย่างไรก็ตามหากไม่สามารถจัดการข้อยกเว้นการโยนซ้ำได้สุดท้ายก็จะไม่ดำเนินการ

ฉันทดสอบตัวอย่างโค้ดนี้ใน VS2010 w / C # 4.0

static void Main()
    {
        Console.WriteLine("Example 1: re-throw inside of another try block:");

        try
        {
            Console.WriteLine("--outer try");
            try
            {
                Console.WriteLine("----inner try");
                throw new Exception();
            }
            catch
            {
                Console.WriteLine("----inner catch");
                throw;
            }
            finally
            {
                Console.WriteLine("----inner finally");
            }
        }
        catch
        {
            Console.WriteLine("--outer catch");
            // swallow
        }
        finally
        {
            Console.WriteLine("--outer finally");
        }
        Console.WriteLine("Huzzah!");

        Console.WriteLine();
        Console.WriteLine("Example 2: re-throw outside of another try block:");
        try
        {
            Console.WriteLine("--try");
            throw new Exception();
        }
        catch
        {
            Console.WriteLine("--catch");
            throw;
        }
        finally
        {
            Console.WriteLine("--finally");
        }

        Console.ReadLine();
    }

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

ตัวอย่างที่ 1: โยนเข้าไปในบล็อกลองอีกครั้ง: - เรา
เตอร์ลอง
---- ลอง
ด้านใน
---- จับด้านใน ---- ด้านในในที่สุด - เรา
เตอร์จับ - เรา
เตอร์ในที่สุด
Huzzah!

ตัวอย่างที่ 2: โยนซ้ำนอกบล็อกลองอื่น:
--try
--catch

Unhandled Exception: System.Exception: Exception of type 'System.Exception' ถูกโยนทิ้ง
ที่ ConsoleApplication1.Program.Main () ใน C: \ local source \ ConsoleApplication1 \ Program.cs: บรรทัดที่ 53


3
จับได้ดีฉันไม่รู้!
Andrew

1
โปรดทราบว่าสุดท้ายแล้วอาจทำงานได้ขึ้นอยู่กับสิ่งที่คุณเลือก: stackoverflow.com/a/46267841/480982
Thomas Weller

2
น่าสนใจ ... บน. NET Core 2.0 ส่วนสุดท้ายทำงานหลังจากข้อยกเว้นที่ไม่สามารถจัดการได้
Mahdi Ghiasi

ที่น่าสนใจก็คือฉันเพิ่งทำการทดสอบทั้งใน. NET Core 2.0 และ. NET Framework 4.6.1 และทั้งคู่ก็รันในที่สุดหลังจากเกิดข้อยกเว้น พฤติกรรมนี้เปลี่ยนไปหรือไม่?
Cameron Bielstein

24

ตัวอย่างของคุณจะทำงานเหมือนกับรหัสนี้:

try {
    try {
        // Do stuff
    } catch(Exception e) {
        throw e;
    }
} finally {
    // Clean up
}

ในฐานะที่เป็นหมายเหตุด้านข้างถ้าคุณหมายถึงจริงๆthrow e;(นั่นคือโยนข้อยกเว้นเดียวกันกับที่คุณเพิ่งจับได้) จะดีกว่ามากที่จะทำthrow;เพราะจะรักษาสแต็กติดตามเดิมแทนที่จะสร้างใหม่


ฉันไม่คิดว่านี่จะถูกต้อง สุดท้ายควรอยู่ในบล็อกลองด้านนอกไม่ใช่ด้านนอก
Matthew Pigram

@MatthewPigram: คุณหมายถึงอะไร? finallyบล็อกในความเป็นจริงจะทำงานหลังจากที่catchบล็อก (แม้ว่าการป้องกันการจับ rethrows ข้อยกเว้น) ซึ่งเป็นสิ่งที่ตัวอย่างข้อมูลของฉันพยายามที่จะแสดงให้เห็นถึง
Daniel Pryden

จากวิธีที่ฉันตีความตัวอย่างของเขาเขาพยายามที่จะลองจับในที่สุดในบล็อกอื่น ในที่สุดก็ลองจับไม่ได้
Matthew Pigram

1
@MatthewPigram: คำตอบของฉันไม่มีโครงสร้างแบบ "ลองจับสุดท้าย" เลย มันมีคำว่า "ลองในที่สุด" และภายในtryบล็อกคำตอบของฉันมี "ลองจับ" ฉันพยายามอธิบายพฤติกรรมของโครงสร้าง 3 ส่วนโดยใช้โครงสร้าง 2 ส่วนสองส่วน ฉันไม่เห็นสัญญาณของtryบล็อกที่สองในคำถามเดิมดังนั้นฉันไม่เข้าใจว่าคุณได้รับสิ่งนั้นมาจากไหน
Daniel Pryden

12

หากมีข้อยกเว้นที่ไม่สามารถจัดการได้ภายในบล็อกตัวจัดการการจับบล็อกจะถูกเรียกว่าเป็นศูนย์ครั้งในที่สุด

  static void Main(string[] args)
  {
     try
     {
        Console.WriteLine("in the try");
        int d = 0;
        int k = 0 / d;
     }
     catch (Exception e)
     {
        Console.WriteLine("in the catch");
        throw;
     }
     finally
     {
        Console.WriteLine("In the finally");
     }
  }

เอาท์พุต:

C: \ users \ administrator \ documents \ TestExceptionNesting \ bin \ Release> TestExceptionNesting.exe

ในการลอง

ในการจับ

Unhandled Exception: System.DivideByZeroException: พยายามหารด้วยศูนย์ ที่ TestExceptionNesting.Program.Main (String [] args) ใน C: \ users \ administrator \ documents \ TestExceptionNesting \ TestExceptionNesting.cs: บรรทัด 22

C: \ users \ administrator \ documents \ TestExceptionNesting \ bin \ release>

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


เว้นแต่ว่าข้อยกเว้นที่โยนจะถูกจับในจุดอื่นที่สูงกว่าสแต็กซึ่งในกรณีนี้อาจดำเนินการได้หากมีการจัดการข้อยกเว้นการโยนนั้น ... หรือฉันอาจจะคิดผิด ...
tomosius

@tomosius ใช่นั่นคือสิ่งที่คำตอบของแบรนดอนอธิบาย :)
Andrew

@tomosius นั่นคือเหตุผลที่ฉันเริ่มต้นด้วยการระบุ "หากมีข้อยกเว้นที่ไม่สามารถจัดการได้" หากข้อยกเว้นการโยนถูกจับได้ที่ไหนสักแห่งตามคำจำกัดความเรากำลังพูดถึงกรณีอื่น
Eusebio Rufian-Zilbermann

นี่ไม่เป็นความจริง. อย่างน้อยก็ไม่ใช่สำหรับ NET Core 3.1 โครงการคอนโซลใหม่ที่เรียบง่ายพร้อมรหัสนี้จะแสดง "ในที่สุด" หลังจากข้อยกเว้น
emzero

น่าสนใจที่พฤติกรรมเปลี่ยนไปแกน. NET ไม่ได้อยู่ด้วยซ้ำเมื่อฉันโพสต์;)
Eusebio Rufian-Zilbermann

2

วิธีง่ายๆในการบอกก็คือการดีบักโค้ดของคุณและสังเกตว่าเมื่อใดที่ถูกเรียกในที่สุด


1

การทดสอบด้วยแอปพลิเคชัน C # Console ในที่สุดก็มีการเรียกใช้โค้ดหลังจากเกิดข้อยกเว้น: มี "กล่องโต้ตอบข้อผิดพลาดของแอปพลิเคชัน" อยู่และหลังจากที่คุณเลือกตัวเลือก "ปิดโปรแกรม" บล็อกในที่สุดก็ถูกดำเนินการในหน้าต่างคอนโซลนั้น แต่การกำหนดจุดแตกหักภายในบล็อกโค้ดในที่สุดฉันก็ไม่สามารถตีมันได้ ดีบักเกอร์หยุดที่คำสั่งโยน นี่คือรหัสทดสอบของฉัน:

    class Program
    {
       static void Main(string[] args)
       {
          string msg;
          Console.WriteLine(string.Format("GetRandomNuber returned: {0}{1}", GetRandomNumber(out msg), msg) == "" ? "" : "An error has occurred: " + msg);
       }

       static int GetRandomNumber(out string errorMessage)
       {
         int result = 0;
         try
         {
            errorMessage = "";
            int test = 0;
            result = 3/test;
            return result;
         }
         catch (Exception ex)
         {
            errorMessage = ex.Message;
            throw ex;

         }
         finally
         {
            Console.WriteLine("finally block!");
         }

       }
    }

การดีบักใน VS2010 - .NET Framework 4.0

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