รหัสที่ไม่สามารถเข้าถึงได้ แต่สามารถเข้าถึงได้โดยมีข้อยกเว้น


108

รหัสนี้เป็นส่วนหนึ่งของแอปพลิเคชันที่อ่านและเขียนไปยังฐานข้อมูลที่เชื่อมต่อ ODBC trueมันจะสร้างบันทึกในฐานข้อมูลและการตรวจสอบแล้วถ้าเป็นประวัติการณ์สร้างเรียบร้อยแล้วกลับมา

ความเข้าใจของฉันเกี่ยวกับโฟลว์การควบคุมมีดังนี้:

command.ExecuteNonQuery()ได้รับการบันทึกไว้เพื่อโยนInvalid​Operation​Exceptionเมื่อ "การเรียกเมธอดไม่ถูกต้องสำหรับสถานะปัจจุบันของวัตถุ" ดังนั้นหากเป็นเช่นนั้นการดำเนินการของtryบล็อกจะหยุดลงfinallyบล็อกจะถูกดำเนินการจากนั้นจะดำเนินการreturn false;ที่ด้านล่าง

อย่างไรก็ตาม IDE ของฉันอ้างว่าreturn false;เป็นรหัสที่ไม่สามารถเข้าถึงได้ และดูเหมือนว่าจะเป็นจริงฉันสามารถลบออกและรวบรวมได้โดยไม่มีข้อตำหนิใด ๆ อย่างไรก็ตามสำหรับฉันดูเหมือนว่าจะไม่มีค่าส่งคืนสำหรับเส้นทางรหัสที่มีการโยนข้อยกเว้นดังกล่าว

private static bool createRecord(String table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1;
    } finally {
        command.Dispose();
    }

    return false;
}

อะไรคือข้อผิดพลาดในการทำความเข้าใจที่นี่?



41
หมายเหตุด้านข้าง: อย่าเรียกDisposeอย่างชัดเจน แต่ให้ใส่using:using (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
Dmitry Bychenko

7
finallyสิ่งที่บล็อกวิธีอื่นมากกว่าที่คุณคิด
Thorbjørn Ravn Andersen

คำตอบ:


149

คำเตือนคอมไพเลอร์ (ระดับ 2) CS0162

ตรวจพบรหัสที่ไม่สามารถเข้าถึงได้

คอมไพลเลอร์ตรวจพบโค้ดที่จะไม่ถูกเรียกใช้งาน

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

หมายเหตุ : คุณสามารถพิสูจน์ข้อเท็จจริงนี้กับตนเองได้โดยพยายามก้าวไปสู่รหัสที่ไม่สามารถเข้าถึงได้ด้วยดีบักเกอร์หรือใช้ IL Explorer

finallyอาจทำงานในข้อยกเว้น (ว่าที่กัน) มันไม่ได้เปลี่ยนความเป็นจริง (ในกรณีนี้) ก็จะยังคงเป็นข้อยกเว้น Uncaught Ergo สุดท้ายreturnจะไม่ถูกโจมตีโดยไม่คำนึงถึง

  • หากคุณต้องการให้โค้ดดำเนินการต่อไปในตัวสุดท้ายreturnตัวเลือกเดียวของคุณคือCatch the Exception ;

  • หากไม่เป็นเช่นนั้นให้ปล่อยให้เป็นแบบนั้นและนำไฟล์return.

ตัวอย่าง

try 
{
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    return returnValue == 1;
}
catch(<some exception>)
{
   // do something
}
finally 
{
    command.Dispose();
}

return false;

เพื่ออ้างอิงเอกสาร

ลองในที่สุด (อ้างอิง C #)

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

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

โดยปกติแล้วเมื่อข้อยกเว้นที่ไม่สามารถจัดการได้สิ้นสุดแอปพลิเคชันการเรียกใช้บล็อกในที่สุดหรือไม่ก็ไม่สำคัญ แต่ถ้าคุณมีงบในบล็อกสุดท้ายที่จะต้องทำงานแม้ในสถานการณ์ที่หนึ่งในการแก้ปัญหาคือการเพิ่มการป้องกันการจับงบลองที่สุด อีกวิธีหนึ่งคือคุณสามารถจับยกเว้นว่าอาจจะมีการโยนในบล็อกลองของงบลองในที่สุดก็สูงขึ้นไปเรียกกอง นั่นคือคุณสามารถตรวจจับข้อยกเว้นในเมธอดที่เรียกใช้เมธอดที่มีคำสั่ง try-ในที่สุดหรือในเมธอดที่เรียกเมธอดนั้นหรือในเมธอดใด ๆ ใน call stack หากไม่พบข้อยกเว้นการดำเนินการบล็อกสุดท้ายขึ้นอยู่กับว่าระบบปฏิบัติการเลือกที่จะทริกเกอร์ข้อยกเว้นเพื่อคลายการดำเนินการ

สุดท้าย

เมื่อใช้สิ่งใดก็ตามที่สนับสนุนIDisposableอินเทอร์เฟซ (ซึ่งออกแบบมาเพื่อปล่อยทรัพยากรที่ไม่มีการจัดการ) คุณสามารถรวมไว้ในusingคำสั่งได้ คอมไพเลอร์จะสร้างtry {} finally {}และเรียกใช้ภายในDispose()วัตถุ


1
คุณหมายถึงอะไรโดย IL ในประโยคแรก?
ลาน

2
@Clockwork ILเป็นผลิตภัณฑ์จากการรวบรวมโค้ดที่เขียนด้วยภาษา. NET ระดับสูง เมื่อคุณรวบรวมโค้ดของคุณที่เขียนด้วยภาษาใดภาษาหนึ่งเหล่านี้คุณจะได้รับไบนารีที่สร้างจาก IL โปรดทราบว่าภาษาระดับกลางบางครั้งเรียกอีกอย่างว่าภาษากลางทั่วไป (CIL) หรือ Microsoft Intermediate Language (MSIL),
Michael Randall

1
ในระยะสั้นเพราะเขาไม่เข้าใจความเป็นไปได้คือ: ไม่ว่าจะลองทำงานจนกว่าจะได้รับผลตอบแทนและไม่สนใจผลตอบแทนด้านล่างในที่สุดหรือมีการโยนข้อยกเว้นและจะไม่มีวันส่งคืนเนื่องจากฟังก์ชันจะออกเนื่องจากมีข้อยกเว้น โยน.
Felype

86

สุดท้ายบล็อกจะถูกดำเนินการจากนั้นจะดำเนินการส่งคืนเท็จ ที่ส่วนลึกสุด.

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

หากคุณต้องการให้ข้อยกเว้นถูกกลืนคุณควรใช้catchบล็อกที่ไม่มีthrowในนั้น


1
sinppet ข้างต้นจะคอมไพล์ในกรณีที่ยกเว้นจะคืนอะไร?
Ehsan Sajjad

3
มันรวบรวม แต่มันจะไม่โดนreturn falseเพราะมันจะโยนข้อยกเว้นแทน @EhsanSajjad
Patrick Hofman

1
ดูเหมือนจะแปลกคอมไพล์เพราะมันจะส่งคืนค่าสำหรับบูลในกรณีที่ไม่มีข้อยกเว้นและในกรณีที่ไม่มีข้อยกเว้นจะไม่มีอะไรเป็นไปตามกฎหมายเพื่อตอบสนองประเภทการส่งคืนของเมธอด?
Ehsan Sajjad

2
คอมไพเลอร์จะละเว้นบรรทัดนั่นคือสิ่งที่เตือนไว้ แล้วทำไมแปลกจัง? @EhsanSajjad
Patrick Hofman

3
ข้อเท็จจริงที่น่าสนใจ: ไม่รับประกันว่าสุดท้ายแล้วการบล็อกจะทำงานหากข้อยกเว้นไม่ติดอยู่ในโปรแกรม ข้อมูลจำเพาะไม่ได้รับประกันนี้และ CLRs ต้นไม่ไม่ดำเนินการบล็อกในที่สุด ฉันคิดว่าเริ่มต้นด้วย 4.0 (อาจจะก่อนหน้านี้) พฤติกรรมเปลี่ยนไป แต่ช่วงเวลาอื่น ๆ อาจยังคงทำงานแตกต่าง ทำให้เกิดพฤติกรรมที่ค่อนข้างน่าแปลกใจ
Voo

27

คำเตือนเป็นเพราะคุณไม่ได้ใช้catchและโดยทั่วไปวิธีการของคุณเขียนไว้ดังนี้:

bool SomeMethod()
{
    return true;
    return false; // CS0162 Unreachable code detected
}

เนื่องจากคุณใช้finallyเพื่อกำจัดเพียงอย่างเดียววิธีแก้ปัญหาที่ต้องการคือการใช้usingรูปแบบ:

using(var command = new WhateverCommand())
{
     ...
}

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

ถ้ามันไม่เกี่ยวกับการกำจัดล่ะก็

try { ...; return true; } // only one return
finally { ... }

ก็เพียงพอแล้วเนื่องจากคุณจะไม่ต้องกลับมาfalseในตอนท้ายของวิธีการ (ไม่จำเป็นสำหรับบรรทัดนั้น) วิธีการของคุณเป็นทั้งผลการดำเนินการกลับมาของคำสั่ง ( trueหรือfalse) หรือจะโยนข้อยกเว้นมิฉะนั้น


พิจารณาที่จะโยนข้อยกเว้นของตัวเองโดยการตัดข้อยกเว้นที่คาดไว้ (ตรวจสอบตัวสร้าง InvalidOperationException ):

try { ... }
catch(SomeExpectedException e)
{
    throw new SomeBetterExceptionWithExplanaition("...", e);
}

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


ส่วนใหญ่แล้วคุณไม่สนใจเกี่ยวกับข้อยกเว้นที่ไม่สามารถจัดการได้ บางครั้งคุณต้องตรวจสอบให้แน่ใจว่าfinallyมีการเรียกใช้แม้ว่าจะไม่มีการจัดการข้อยกเว้นก็ตาม ในกรณีนี้คุณเพียงแค่จับมันเองแล้วโยนใหม่ (ดูคำตอบนี้ ):

try { ... }
catch { ...; throw; } // re-throw
finally { ... }

14

ดูเหมือนว่าคุณกำลังมองหาสิ่งนี้:

private static bool createRecord(string table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {
  [... some other code ...]

  // Using: do not call Dispose() explicitly, but wrap IDisposable into using
  using (var command = ...) {
    try {
      // Normal flow:
      command.CommandText = sb.ToString();

      // True if and only if exactly one record affected
      return command.ExecuteNonQuery() == 1;
    }
    catch (DbException) {
      // Exceptional flow (all database exceptions)
      return false;
    }
  }
}

โปรดทราบfinally ว่าไม่ได้กลืนข้อยกเว้นใด ๆ

finally {
  // This code will be executed; the exception will be efficently re-thrown
}

// And this code will never be reached

8

คุณไม่มีcatchบล็อกดังนั้นข้อยกเว้นจึงยังคงถูกโยนทิ้งซึ่งบล็อกการส่งคืน

สุดท้ายบล็อกจะถูกดำเนินการจากนั้นจะดำเนินการส่งคืนเท็จ ที่ส่วนลึกสุด.

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

finallyบล็อกถูกใช้สำหรับการล้างข้อมูลและไม่พบข้อยกเว้น ข้อยกเว้นจะถูกโยนก่อนการส่งคืนดังนั้นการส่งคืนจะไม่มีวันถึงเพราะมีการโยนข้อยกเว้นมาก่อน

IDE ของคุณถูกต้องที่จะไม่สามารถเข้าถึงได้เนื่องจากข้อยกเว้นจะถูกโยนทิ้ง เฉพาะcatchบล็อกเท่านั้นที่สามารถจับข้อยกเว้นได้

อ่านจากเอกสาร ,

โดยปกติแล้วเมื่อข้อยกเว้นที่ไม่สามารถจัดการได้สิ้นสุดแอปพลิเคชันการเรียกใช้บล็อกในที่สุดหรือไม่ก็ไม่สำคัญ แต่ถ้าคุณมีงบในบล็อกสุดท้ายที่จะต้องทำงานแม้ในสถานการณ์ที่หนึ่งแก้ปัญหาคือการเพิ่มการป้องกันการจับงบลองที่สุด หรือคุณสามารถตรวจจับข้อยกเว้นที่อาจเกิดขึ้นในบล็อก try ของคำสั่ง try-final ที่สูงขึ้นใน call stack นั่นคือคุณสามารถตรวจจับข้อยกเว้นในเมธอดที่เรียกใช้เมธอดที่มีคำสั่ง try-ในที่สุดหรือในเมธอดที่เรียกเมธอดนั้นหรือในเมธอดใด ๆ ใน call stack หากยกเว้นไม่ได้ถูกจับ, การดำเนินการของกลุ่มสุดท้ายขึ้นอยู่กับว่าจะเลือกระบบปฏิบัติการที่จะเรียกข้อยกเว้นดำเนินการคลี่คลาย

สิ่งนี้แสดงให้เห็นอย่างชัดเจนว่าสุดท้ายแล้วไม่ได้ตั้งใจที่จะจับข้อยกเว้นและคุณจะถูกต้องหากมีข้อความว่างcatchก่อนfinallyคำสั่ง


7

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

ดังนั้นreturn falseจะไม่มีวันดำเนินการ

ลองโยนข้อยกเว้นด้วยตนเองเพื่อทำความเข้าใจขั้นตอนการควบคุม:

try {
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    // Try this.
    throw new Exception("See where this goes.");

    return returnValue == 1;
} finally {
    command.Dispose();
}

5

ในรหัสของคุณ:

private static bool createRecord(String table, IDictionary<String,String> data, System.Data.IDbConnection conn, OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1; // You return here in case no exception is thrown
    } finally {
        command.Dispose(); //You don't have a catch so the exception is passed on if thrown
    }

    return false; // This is never executed because there was either one of the above two exit points of the method reached.
}

สุดท้ายบล็อกจะถูกดำเนินการจากนั้นจะดำเนินการส่งคืนเท็จ ที่ส่วนลึกสุด

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


4

คำสั่งสุดท้ายreturn falseไม่สามารถเข้าถึงได้เนื่องจาก try block ไม่มีcatchส่วนที่จะจัดการกับข้อยกเว้นดังนั้นข้อยกเว้นจะถูกเปลี่ยนใหม่หลังจากfinallyบล็อกและการดำเนินการจะไม่ถึงคำสั่งสุดท้าย


2

คุณมีเส้นทางส่งคืนสองเส้นทางในรหัสของคุณซึ่งเส้นทางที่สองไม่สามารถเข้าถึงได้เนื่องจากเส้นทางแรก ข้อความสุดท้ายในtryบล็อกของคุณreturn returnValue == 1;ให้ผลตอบแทนตามปกติดังนั้นคุณจึงไม่สามารถไปถึงreturn false;จุดสิ้นสุดของบล็อกเมธอดได้

FWIW ลำดับการออกที่เกี่ยวข้องกับfinallyบล็อกคือ: นิพจน์ที่ให้ค่าส่งคืนในบล็อก try จะได้รับการประเมินก่อนจากนั้นบล็อกสุดท้ายจะถูกเรียกใช้งานจากนั้นค่านิพจน์ที่คำนวณจะถูกส่งกลับ (ภายในบล็อกการลอง)

เกี่ยวกับโฟลว์ยกเว้น ... โดยไม่มีcatchการfinallyจะดำเนินการตามข้อยกเว้นก่อนที่ข้อยกเว้นจะถูกลบออกจากเมธอด ไม่มีเส้นทาง "กลับ"

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