การจับข้อยกเว้นด้วย "จับเมื่อ"


100

ฉันเจอคุณลักษณะใหม่นี้ใน C # ซึ่งช่วยให้ตัวจัดการการจับสามารถดำเนินการได้เมื่อตรงตามเงื่อนไขที่กำหนด

int i = 0;
try
{
    throw new ArgumentNullException(nameof(i));
}
catch (ArgumentNullException e)
when (i == 1)
{
    Console.WriteLine("Caught Argument Null Exception");
}

ฉันพยายามทำความเข้าใจว่าสิ่งนี้อาจเป็นประโยชน์เมื่อใด

สถานการณ์หนึ่งอาจเป็นดังนี้:

try
{
    DatabaseUpdate()
}
catch (SQLException e)
when (driver == "MySQL")
{
    //MySQL specific error handling and wrapping up the exception
}
catch (SQLException e)
when (driver == "Oracle")
{
    //Oracle specific error handling and wrapping up of exception
}
..

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

อีกสถานการณ์หนึ่งที่ฉันคิดได้ก็คือ:

try
{
    SomeOperation();
}
catch(SomeException e)
when (Condition == true)
{
    //some specific error handling that this layer can handle
}
catch (Exception e) //catchall
{
    throw;
}

อีกครั้งนี่คือสิ่งที่ฉันสามารถทำได้เช่น:

try
{
    SomeOperation();
}
catch(SomeException e)
{
    if (condition == true)
    {
        //some specific error handling that this layer can handle
    }
    else
        throw;
}

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


9
จะมีประโยชน์หากwhenจำเป็นต้องเข้าถึงข้อยกเว้นเอง
Tim Schmelter

1
แต่นั่นก็เป็นสิ่งที่เราสามารถทำได้ภายในตัวจัดการเองเช่นกัน มีประโยชน์นอกเหนือจาก 'รหัสที่เป็นระเบียบมากขึ้นเล็กน้อย' หรือไม่?
MS Srikkanth

3
แต่คุณได้จัดการข้อยกเว้นที่คุณไม่ต้องการแล้ว ถ้าคุณต้องการจับมันที่อื่นในนี้try..catch...catch..catch..finally?
Tim Schmelter

4
@ user3493289: ตามอาร์กิวเมนต์นั้นเราไม่จำเป็นต้องมีการตรวจสอบประเภท automatich ในตัวจัดการข้อยกเว้นเช่นกัน: เราสามารถอนุญาตcatch (Exception ex)ตรวจสอบประเภทและthrowอื่น ๆ เท่านั้น โค้ดที่เป็นระเบียบมากขึ้นเล็กน้อย (หรือที่เรียกว่าการหลีกเลี่ยงเสียงรบกวนของโค้ด) เป็นสาเหตุที่ทำให้คุณลักษณะนี้มี (นี่เป็นเรื่องจริงสำหรับคุณสมบัติมากมาย)
Heinzi

2
@TimSchmelter ขอบคุณ โพสต์เป็นคำตอบและฉันจะยอมรับมัน ดังนั้นสถานการณ์จริงจะเป็น 'ถ้าเงื่อนไขในการจัดการขึ้นอยู่กับข้อยกเว้น' ให้ใช้คุณสมบัตินี้ /
MS Srikkanth

คำตอบ:


126

Catch Blocks ช่วยให้คุณสามารถกรองประเภทของข้อยกเว้นได้แล้ว:

catch (SomeSpecificExceptionType e) {...}

whenข้อช่วยให้คุณสามารถที่จะขยายตัวกรองนี้เพื่อแสดงออกทั่วไป

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


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

นี่คือกรณีที่ฉันใช้จริง (ใน VB ซึ่งมีคุณสมบัตินี้มาระยะหนึ่งแล้ว):

try
{
    SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
    // Handle the *specific* error I was expecting. 
}

เช่นเดียวกันSqlExceptionซึ่งมีErrorCodeคุณสมบัติ ทางเลือกจะเป็นดังนี้:

try
{
    SomeLegacyComOperation();
}
catch (COMException e)
{
    if (e.ErrorCode == 0x1234)
    {
        // Handle error
    }
    else
    {
        throw;
    }
}

ซึ่งเป็นเนื้อหาที่สง่างามน้อยลงและเล็กน้อยแบ่งกองติดตาม

นอกจากนี้คุณสามารถพูดถึงข้อยกเว้นประเภทเดียวกันได้สองครั้งใน try-catch-block เดียวกัน:

try
{
    SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
    ...
}
catch (COMException e) when (e.ErrorCode == 0x5678)
{
    ...
}

ซึ่งจะเป็นไปไม่ได้หากไม่มีwhenเงื่อนไข


2
แนวทางที่สองยังไม่อนุญาตให้จับในรูปแบบอื่นcatchใช่หรือไม่?
Tim Schmelter

@TimSchmelter. จริง. คุณต้องจัดการCOMExceptions ทั้งหมดในบล็อกเดียวกัน
Heinzi

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

1
เท่าที่ฉันกังวลส่วนที่ตามหลัง "สรุป:" ควรเป็นบรรทัดแรกของคำตอบ
CompuChip

1
@ user3493289: นั่นมักจะเป็นกรณีที่มีรหัสที่น่าเกลียด คุณคิดว่า "ฉันไม่ควรยุ่งเรื่องนี้ตั้งแต่แรกออกแบบโค้ดใหม่" และคุณก็คิดว่า "อาจมีวิธีที่จะรองรับการออกแบบนี้อย่างหรูหราออกแบบภาษาใหม่" ในกรณีนี้มีเกณฑ์ประเภทหนึ่งสำหรับความน่าเกลียดที่คุณต้องการให้ชุดประโยคจับของคุณเป็นดังนั้นสิ่งที่ทำให้สถานการณ์บางอย่างน่าเกลียดน้อยลงช่วยให้คุณทำสิ่งต่างๆได้มากขึ้นภายในเกณฑ์ของคุณ :-)
Steve Jessop

41

จากวิกิของ Roslyn (เน้นของฉัน):

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

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

private static bool Log(Exception e) { /* log it */ ; return false; }

… try { … } catch (Exception e) when (Log(e)) { }

จุดแรกเป็นสิ่งที่ควรค่าแก่การสาธิต

static class Program
{
    static void Main(string[] args)
    {
        A(1);
    }

    private static void A(int i)
    {
        try
        {
            B(i + 1);
        }
        catch (Exception ex)
        {
            if (ex.Message != "!")
                Console.WriteLine(ex);
            else throw;
        }
    }

    private static void B(int i)
    {
        throw new Exception("!");
    }
}

หากเราเรียกใช้สิ่งนี้ใน WinDbg จนกว่าจะถึงข้อยกเว้นและพิมพ์สแต็กโดยใช้!clrstack -i -aเราจะเห็นเพียงแค่กรอบของA:

003eef10 00a7050d [DEFAULT] Void App.Program.A(I4)

PARAMETERS:
  + int i  = 1

LOCALS:
  + System.Exception ex @ 0x23e3178
  + (Error 0x80004005 retrieving local variable 'local_1')

อย่างไรก็ตามหากเราเปลี่ยนโปรแกรมที่จะใช้when:

catch (Exception ex) when (ex.Message != "!")
{
    Console.WriteLine(ex);
}

เราจะเห็นสแต็กประกอบด้วยBเฟรมของ:

001af2b4 01fb05aa [DEFAULT] Void App.Program.B(I4)

PARAMETERS:
  + int i  = 2

LOCALS: (none)

001af2c8 01fb04c1 [DEFAULT] Void App.Program.A(I4)

PARAMETERS:
  + int i  = 1

LOCALS:
  + System.Exception ex @ 0x2213178
  + (Error 0x80004005 retrieving local variable 'local_1')

ข้อมูลดังกล่าวจะมีประโยชน์อย่างมากเมื่อทำการดีบักข้อมูลการชน


8
นั่นทำให้ฉันประหลาดใจ จะไม่throw;(ตรงข้ามกับthrow ex;) ปล่อยให้สแต็กไม่เป็นอันตรายเช่นกัน? +1 สำหรับผลข้างเคียง ฉันไม่แน่ใจว่าฉันเห็นด้วยกับสิ่งนั้น แต่เป็นการดีที่จะรู้เกี่ยวกับเทคนิคนั้น
Heinzi

14
มันไม่ผิด - นี่ไม่ได้หมายถึงการติดตามสแต็ก- มันหมายถึงสแต็กเอง หากคุณดูสแต็กในดีบักเกอร์ (WinDbg) และแม้ว่าคุณจะใช้throw;สแต็กจะคลายตัวและคุณสูญเสียค่าพารามิเตอร์ไป
Eli Arbel

1
สิ่งนี้มีประโยชน์อย่างมากเมื่อทำการดีบักทิ้ง
Eli Arbel

3
@Heinzi ดูคำตอบของฉันในเธรดอื่นซึ่งคุณจะเห็นว่ามีthrow;การเปลี่ยนแปลงการติดตามสแต็กเล็กน้อยและthrow ex;เปลี่ยนแปลงไปมาก
Jeppe Stig Nielsen

1
การใช้throwจะรบกวนการติดตามสแต็กเล็กน้อย หมายเลขบรรทัดจะแตกต่างกันเมื่อใช้เมื่อเทียบกับthrow when
Mike Zboray

7

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

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

สุดท้ายหากสาย 23 และ 27 ของการfooโทรbarและการโทรในบรรทัดที่ 23 เกิดข้อยกเว้นซึ่งถูกจับได้ภายในfooและเปลี่ยนใหม่ในบรรทัด 57 การติดตามสแต็กจะแนะนำว่ามีข้อยกเว้นเกิดขึ้นขณะโทรbarจากบรรทัด 57 [ตำแหน่งของการโทรซ้ำ] ทำลายข้อมูลใด ๆ ว่ามีข้อยกเว้นเกิดขึ้นในสาย -23 หรือสาย -27 หรือไม่ การใช้whenเพื่อหลีกเลี่ยงการจับข้อยกเว้นตั้งแต่แรกเพื่อหลีกเลี่ยงการรบกวนดังกล่าว

BTW รูปแบบที่มีประโยชน์ซึ่งน่าอึดอัดใจทั้งใน C # และ VB.NET คือการใช้การเรียกใช้ฟังก์ชันภายในwhenประโยคเพื่อตั้งค่าตัวแปรที่สามารถใช้ภายในfinallyประโยคเพื่อพิจารณาว่าฟังก์ชันทำงานตามปกติหรือไม่เพื่อจัดการกับกรณีที่ฟังก์ชัน ไม่มีความหวังที่จะ "แก้ไข" ข้อยกเว้นใด ๆ ที่เกิดขึ้น แต่ต้องดำเนินการตามนั้น ตัวอย่างเช่นหากมีข้อยกเว้นเกิดขึ้นภายในเมธอดของโรงงานซึ่งควรจะส่งคืนอ็อบเจ็กต์ที่ห่อหุ้มทรัพยากรทรัพยากรใด ๆ ที่ได้มาจะต้องถูกปล่อยออกไป แต่ข้อยกเว้นที่อยู่ภายใต้ควรจะแพร่กระจายไปยังผู้เรียกใช้ วิธีที่สะอาดที่สุดในการจัดการกับความหมาย (แม้ว่าจะไม่ใช่ทางไวยากรณ์) คือการมีไฟล์finallyบล็อกตรวจสอบว่ามีข้อยกเว้นเกิดขึ้นหรือไม่และหากเป็นเช่นนั้นให้ปล่อยทรัพยากรทั้งหมดที่ได้มาในนามของอ็อบเจ็กต์ที่จะไม่ถูกส่งคืนอีกต่อไป เนื่องจากรหัสการล้างข้อมูลไม่มีความหวังในการแก้ไขเงื่อนไขใด ๆ ที่ทำให้เกิดข้อยกเว้นจึงไม่ควรcatchทำ แต่เพียงต้องการทราบว่าเกิดอะไรขึ้น เรียกใช้ฟังก์ชันเช่น:

bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second)
{
  first = second;
  return false;
}

ภายในwhenประโยคจะทำให้ฟังก์ชันโรงงานรู้ว่ามีบางอย่างเกิดขึ้น

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