Kode tidak dapat dijangkau, tetapi dapat dijangkau dengan pengecualian

108

Kode ini adalah bagian dari aplikasi yang membaca dari dan menulis ke database yang terhubung dengan ODBC. Ini membuat rekaman dalam database dan kemudian memeriksa apakah rekaman telah berhasil dibuat, kemudian kembali true.

Pemahaman saya tentang aliran kendali adalah sebagai berikut:

command.ExecuteNonQuery()didokumentasikan untuk memunculkan Invalid​Operation​Exceptionketika "panggilan metode tidak valid untuk status objek saat ini". Oleh karena itu, jika itu terjadi, eksekusi tryblok akan berhenti, finallyblok akan dieksekusi, kemudian akan dieksekusi return false;di bagian bawah.

Namun, IDE saya mengklaim bahwa return false;kode tersebut tidak dapat dijangkau. Dan tampaknya benar, saya dapat menghapusnya dan mengkompilasi tanpa keluhan. Namun, bagi saya sepertinya tidak akan ada nilai kembali untuk jalur kode tempat pengecualian yang disebutkan dilemparkan.

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;
}

Apa kesalahan pemahaman saya di sini?

0xCAFEBABE
sumber
41
Catatan sampingan: jangan menelepon Disposesecara eksplisit, tetapi tuliskan using:using (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
Dmitry Bychenko
7
Sebuah finallyblok berarti sesuatu yang lain dari yang Anda pikirkan.
Thorbjørn Ravn Andersen

Jawaban:

149

Peringatan Penyusun (level 2) CS0162

Kode tidak terjangkau terdeteksi

Kompilator mendeteksi kode yang tidak akan pernah dijalankan.

Yang hanya mengatakan, Compiler cukup memahami melalui Analisis Statis sehingga tidak dapat dijangkau dan sepenuhnya menghilangkannya dari IL yang dikompilasi (karenanya peringatan Anda)

Catatan : Anda dapat membuktikan fakta ini kepada diri Anda sendiri dengan mencoba Menginjak ke Kode Tak Terjangkau dengan debugger, atau menggunakan Penjelajah IL

The finallydapat berjalan pada Exception , (meskipun samping) itu tidak mengubah fakta (dalam kasus ini) masih akan menjadi Exception Tertangkap . Ergo, yang terakhir returntidak akan pernah terkena.

  • Jika Anda ingin kode untuk melanjutkan ke terakhir return, satu-satunya pilihan Anda adalah untuk menangkap para Exception ;

  • Jika tidak, biarkan saja apa adanya dan hapus file return.

Contoh

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

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

return false;

Mengutip dokumentasi

coba-akhirnya (Referensi C #)

Dengan menggunakan blok terakhir, Anda dapat membersihkan sumber daya apa pun yang dialokasikan dalam blok percobaan, dan Anda dapat menjalankan kode bahkan jika pengecualian terjadi di blok percobaan. Biasanya, pernyataan dari blok terakhir dijalankan ketika kontrol meninggalkan pernyataan percobaan. Pengalihan kontrol dapat terjadi sebagai hasil dari eksekusi normal, eksekusi break, continue, goto, atau return statement, atau propagasi pengecualian dari pernyataan try.

Dalam pengecualian yang ditangani, blok akhirnya yang terkait dijamin akan dijalankan. Namun, jika pengecualian tidak ditangani, eksekusi blok akhirnya bergantung pada bagaimana operasi pelepasan pengecualian dipicu. Itu, pada gilirannya, tergantung pada bagaimana komputer Anda diatur.

Biasanya, ketika pengecualian yang tidak tertangani mengakhiri aplikasi, apakah blok terakhir dijalankan atau tidak tidak penting. Namun, jika Anda memiliki pernyataan di blok akhirnya yang harus dijalankan bahkan dalam situasi itu, salah satu solusinya adalah menambahkan blok catch ke pernyataan coba-akhirnya . Atau, Anda dapat menangkap pengecualian yang mungkin dilemparkan ke blok percobaan dari pernyataan coba-akhirnya yang lebih tinggi dari tumpukan panggilan . Artinya, Anda bisa menangkap pengecualian dalam metode yang memanggil metode yang berisi pernyataan coba-akhirnya, atau dalam metode yang memanggil metode itu, atau dalam metode apa pun dalam tumpukan panggilan. Jika pengecualian tidak tertangkap, eksekusi blok akhirnya bergantung pada apakah sistem operasi memilih untuk memicu operasi pelepasan pengecualian.

akhirnya

Saat menggunakan apa pun yang mendukung IDisposableantarmuka (yang dirancang untuk melepaskan sumber daya yang tidak terkelola), Anda dapat membungkusnya dalam sebuah usingpernyataan. Kompilator akan menghasilkan try {} finally {}panggilan internal dan Dispose()objek

Michael Randall
sumber
1
Apa yang Anda maksud dengan IL pada kalimat pertama?
Jarum jam
2
@Clockwork IL adalah produk kompilasi kode yang ditulis dalam bahasa .NET tingkat tinggi. Setelah Anda mengkompilasi kode Anda yang ditulis dalam salah satu bahasa ini, Anda akan mendapatkan biner yang terbuat dari IL. Perhatikan bahwa Intermediate Language terkadang juga disebut Common Intermediate Language (CIL) atau Microsoft Intermediate Language (MSIL).,
Michael Randall
1
Singkatnya, karena dia tidak menangkap kemungkinan adalah: Entah percobaan berjalan sampai mencapai kembali dan dengan demikian mengabaikan pengembalian di bawah akhirnya ATAU pengecualian dilemparkan dan pengembalian itu tidak pernah tercapai karena fungsi akan keluar karena ada pengecualian dilempar.
Felype
86

blok terakhir akan dieksekusi, lalu akan mengeksekusi return false; di dasar.

Salah. finallytidak menelan pengecualian. Itu menghormatinya dan pengecualian akan dilemparkan seperti biasa. Ini hanya akan mengeksekusi kode pada akhirnya sebelum blok berakhir (dengan atau tanpa pengecualian).

Jika Anda ingin pengecualian ditelan, Anda harus menggunakan catchblok tanpa throwdi dalamnya.

Patrick Hofman
sumber
1
akan kompilasi sinppet di atas dalam kasus pengecualian, apa yang akan dikembalikan?
Ehsan Sajjad
3
Itu memang dapat dikompilasi, tetapi tidak akan pernah berhasil return falsekarena akan memunculkan pengecualian sebagai gantinya @EhsanSajjad
Patrick Hofman
1
tampak aneh, mengkompilasi karena baik itu akan mengembalikan nilai untuk bool dalam kasus tanpa pengecualian dan dalam kasus pengecualian tidak akan ada, jadi sah untuk memenuhi jenis kembalian metode?
Ehsan Sajjad
2
Kompilator hanya akan mengabaikan baris tersebut, untuk itulah peringatan itu ditujukan. Jadi kenapa itu aneh? @EhsanSajjad
Patrick Hofman
3
Fakta menarik: Sebenarnya tidak ada jaminan bahwa blok terakhir akan berjalan jika pengecualian tidak tertangkap dalam program. Spesifikasi tidak menjamin ini dan CLR awal TIDAK mengeksekusi blok terakhir. Saya pikir mulai dengan 4.0 (mungkin lebih awal) perilaku itu berubah, tetapi runtime lain mungkin masih berperilaku berbeda. Membuat perilaku yang agak mengejutkan.
Voo
27

Peringatannya adalah karena Anda tidak menggunakan catchdan metode Anda pada dasarnya ditulis seperti ini:

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

Karena Anda finallyhanya menggunakan untuk membuang, solusi yang disukai adalah menggunakan usingpola:

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

Itu cukup, untuk memastikan apa yang Disposeakan dipanggil. Ini dijamin akan disebut baik setelah keberhasilan pelaksanaan blok kode atau atas (sebelum) beberapa catch turun dalam panggilan stack (panggilan orang tua turun, kan?).

Jika bukan tentang membuang, maka

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

sudah cukup, karena Anda tidak perlu kembali falsedi akhir metode (baris itu tidak diperlukan). Metode Anda adalah hasil kembalian dari eksekusi perintah ( trueatau false) atau akan memunculkan pengecualian jika tidak .


Pertimbangkan juga untuk menampilkan pengecualian sendiri dengan menggabungkan pengecualian yang diharapkan (lihat konstruktor InvalidOperationException ):

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

Ini biasanya digunakan untuk mengatakan sesuatu yang lebih berarti (berguna) kepada pemanggil daripada yang akan diceritakan oleh pengecualian panggilan bersarang.


Sering kali Anda tidak terlalu peduli dengan pengecualian yang tidak tertangani. Terkadang Anda perlu memastikan bahwa finallydipanggil meskipun pengecualian tidak ditangani. Dalam hal ini Anda cukup menangkapnya sendiri dan membuangnya kembali (lihat jawaban ini ):

try { ... }
catch { ...; throw; } // re-throw
finally { ... }
Sinatr
sumber
14

Sepertinya, Anda mencari sesuatu seperti ini:

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;
    }
  }
}

Harap perhatikan, itu finally tidak menelan pengecualian apa pun

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

// And this code will never be reached
Dmitry Bychenko
sumber
8

Anda tidak memiliki catchblok, jadi pengecualian masih dilempar, yang memblokir pengembalian.

blok terakhir akan dieksekusi, lalu akan mengeksekusi return false; di dasar.

Ini salah, karena blok terakhir akan dieksekusi, dan kemudian akan ada pengecualian yang tidak tertangkap.

finallyblok digunakan untuk pembersihan, dan mereka tidak menangkap pengecualian. Pengecualian dilemparkan sebelum pengembalian, oleh karena itu, pengembalian tidak akan pernah tercapai, karena pengecualian dilemparkan sebelumnya.

IDE Anda benar sehingga tidak akan pernah tercapai, karena pengecualian akan dibuang. Hanya catchblok yang dapat menangkap pengecualian.

Membaca dari dokumentasi ,

Biasanya, ketika pengecualian yang tidak tertangani mengakhiri aplikasi, apakah blok terakhir dijalankan atau tidak tidak penting. Namun, jika Anda memiliki pernyataan di blok akhirnya yang harus dijalankan bahkan dalam situasi itu, salah satu solusinya adalah menambahkan blok catch ke pernyataan coba-akhirnya . Atau, Anda dapat menangkap pengecualian yang mungkin dilemparkan ke blok percobaan dari pernyataan coba-akhirnya yang lebih tinggi dari tumpukan panggilan. Artinya, Anda bisa menangkap pengecualian dalam metode yang memanggil metode yang berisi pernyataan coba-akhirnya, atau dalam metode yang memanggil metode itu, atau dalam metode apa pun dalam tumpukan panggilan. Jika pengecualian tidak tertangkap, eksekusi blok akhirnya bergantung pada apakah sistem operasi memilih untuk memicu operasi pelepasan pengecualian .

Ini jelas menunjukkan bahwa akhirnya tidak dimaksudkan untuk menangkap pengecualian, dan Anda akan benar jika ada catchpernyataan kosong sebelum finallypernyataan itu.

Ray Wu
sumber
7

Ketika pengecualian dilemparkan, tumpukan akan melepas (eksekusi akan keluar dari fungsi) tanpa mengembalikan nilai, dan blok penangkap apa pun dalam bingkai tumpukan di atas fungsi akan menangkap pengecualian sebagai gantinya.

Karenanya, return falsetidak akan pernah mengeksekusi.

Coba berikan pengecualian secara manual untuk memahami aliran kontrol:

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

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

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

Pada kode Anda:

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.
}

blok terakhir akan dieksekusi, lalu akan mengeksekusi return false; di dasar

Ini adalah kelemahan dalam logika Anda karena finallyblok tidak akan menangkap pengecualian dan tidak akan pernah mencapai pernyataan pengembalian terakhir.

meJustAndrew
sumber
4

Pernyataan terakhir return falsetidak dapat dijangkau, karena blok percobaan kehilangan catchbagian yang akan menangani pengecualian, sehingga pengecualian muncul kembali setelah finallyblok dan eksekusi tidak pernah mencapai pernyataan terakhir.

Martin Staufcik
sumber
2

Anda memiliki dua jalur pengembalian dalam kode Anda, yang kedua tidak dapat dijangkau karena yang pertama. Pernyataan terakhir di tryblok Anda return returnValue == 1;memberikan pengembalian normal Anda, jadi Anda tidak akan pernah bisa mencapai return false;di akhir blok metode.

FWIW, urutan pemeriksaan yang terkait dengan finallyblok tersebut adalah: ekspresi yang memberikan nilai pengembalian dalam blok percobaan akan dievaluasi terlebih dahulu, kemudian blok terakhir akan dieksekusi, dan kemudian nilai ekspresi yang dihitung akan dikembalikan (di dalam blok percobaan).

Mengenai aliran pada pengecualian ... tanpa a catch, finallyakan dieksekusi setelah pengecualian sebelum pengecualian tersebut kemudian dibuang kembali dari metode; tidak ada jalur "kembali".

C Robinson
sumber