Mengapa pernyataan 'lanjutkan' tidak bisa berada di dalam blok 'akhirnya'?

107

Saya tidak punya masalah; Saya hanya penasaran. Bayangkan skenario berikut:

foreach (var foo in list)
{
    try
    {
         //Some code
    }
    catch (Exception)
    {
        //Some more code
    }
    finally
    {
        continue;
    }
}

Ini tidak dapat dikompilasi, karena ini menimbulkan kesalahan kompiler CS0157 :

Kontrol tidak dapat meninggalkan isi klausa akhirnya

Mengapa?

lpaloub
sumber
7
Begitu. Hanya penasaran. Jika masuk akal bagi Anda mengapa itu tidak dapat dikompilasi, mengapa Anda ingin seseorang menjelaskan apa yang sudah masuk akal? =)
J. Steen
14
mengapa Anda akan membutuhkan continue;di finallyblok? Bukankah sama dengan continue;setelah try -- catchblok?
bansi
6
@ J.Steen Tidak, saya TAHU bahwa finally/continueini adalah batasan dari compiler C # :-) Saya juga penasaran dengan alasan dari batasan ini.
xanatos
5
@xanatos - Secara teknis, ini adalah batasan dari CIL yang mendasarinya. Dari spesifikasi bahasa : "Transfer kontrol tidak pernah diizinkan untuk memasukkan penangan tangkapan atau klausa terakhir kecuali melalui mekanisme penanganan pengecualian." dan "Pengalihan kendali dari wilayah yang dilindungi hanya diizinkan melalui instruksi pengecualian (tinggalkan, end.filter, end.catch, atau end.finally)." The brkeluarga instruksi cabang tidak dapat melakukannya.
Unsigned
1
@Unsigned: ini adalah batasan yang bagus juga, memungkinkannya akan menjadi aneh :)
user7116

Jawaban:

150

finallyblok berjalan baik pengecualian dilempar atau tidak. Jika pengecualian dilemparkan, apa yang akan terjadicontinue dilakukan? Anda tidak dapat melanjutkan eksekusi loop, karena pengecualian yang tidak tertangkap akan mentransfer kontrol ke fungsi lain.

Bahkan jika tidak ada pengecualian dilempar, finallyakan berjalan ketika pernyataan transfer kontrol lain di dalam blok coba / tangkap dijalankan, sepertireturn , misalnya, yang membawa masalah yang sama.

Singkatnya, dengan semantik finallytidak masuk akal untuk mengizinkan transfer kontrol dari dalam afinally blok ke luarnya.

Mendukung ini dengan beberapa semantik alternatif akan lebih membingungkan daripada membantu, karena ada solusi sederhana yang membuat perilaku yang diinginkan menjadi lebih jelas. Jadi Anda mendapatkan kesalahan, dan dipaksa untuk memikirkan masalah Anda dengan benar. Ini adalah ide umum "melemparkan Anda ke lubang kesuksesan" yang terjadi di C #.

C #, Anda, dan keluar jika sukses

Jika Anda ingin mengabaikan pengecualian (lebih sering daripada bukan bukan ide yang buruk) dan terus menjalankan loop, gunakan blok catch all:

foreach ( var in list )
{
    try{
        //some code
    }catch{
        continue;
    }
}

Jika Anda ingin continuehanya ketika tidak ada pengecualian yang tidak tertangkap dilemparkan, cukup letakkan di continueluar blok percobaan.

R. Martinho Fernandes
sumber
12
Saya akan menerima ini sebagai jawabannya, karena Anda memahami alasan mengapa Microsoft mungkin memutuskan untuk tidak melanjutkan pada akhirnya. Mungkin gambar itu juga meyakinkan saya :)
lpaloub
20
dapatkah Anda menguraikan gagasan "melemparkan Anda ke lubang kesuksesan"? Saya tidak mengerti :-D
Ant
13
Gambar itu dibuat oleh Jon Skeet, btw. Saya dapatkan dari sini: msmvps.com/blogs/jon_skeet/archive/2010/09/02/…
R. Martinho Fernandes
1
Tentu saja, continuedalam finallyblok akan baik-baik saja jika hanya melanjutkan loop lokal yang didefinisikan di dalam finallyblok. Namun, dalam pertanyaan, ia mencoba melanjutkan loop "luar". Pernyataan serupa yang tidak dapat Anda miliki di dalam finallyblok adalah return, break(saat keluar dari blok) dan goto(saat pergi ke label di luar finallyblok). Untuk diskusi Java terkait, lihat Kembali dari blok akhirnya di Java .
Jeppe Stig Nielsen
32

Berikut adalah sumber yang dapat dipercaya:

Pernyataan lanjutkan tidak dapat keluar dari blok akhirnya (Bagian 8.10). Ketika pernyataan lanjutan terjadi dalam blok akhirnya, target pernyataan lanjutan harus berada dalam blok akhirnya yang sama; jika tidak, kesalahan waktu kompilasi terjadi.

Ini diambil dari MSDN, 8.9.2 Pernyataan lanjutan .

Dokumentasinya mengatakan bahwa:

Pernyataan dari blok terakhir selalu dieksekusi ketika kontrol meninggalkan pernyataan percobaan. Ini benar apakah transfer kontrol terjadi sebagai hasil dari eksekusi normal, sebagai hasil dari eksekusi pernyataan break, continue, goto, atau return, atau sebagai akibat dari menyebarkan pengecualian dari pernyataan percobaan. Jika pengecualian dilemparkan selama eksekusi blok akhirnya, pengecualian tersebut disebarkan ke pernyataan coba terlampir berikutnya. Jika pengecualian lain sedang dalam proses disebarkan, pengecualian itu hilang. Proses menyebarkan pengecualian dibahas lebih lanjut dalam deskripsi pernyataan lemparan (Bagian 8.9.5).

Dari sini 8.10 Pernyataan percobaan .

Mortalus
sumber
31

Anda mungkin berpikir itu masuk akal, tetapi sebenarnya tidak masuk akal .

foreach (var v in List)
{
    try
    {
        //Some code
    }
    catch (Exception)
    {
        //Some more code
        break; or return;
    }
    finally
    {
        continue;
    }
}

Apa yang ingin Anda lakukan jeda atau lanjutkan saat pengecualian dilemparkan? Tim penyusun C # tidak ingin membuat keputusan sendiri dengan asumsi breakataucontinue . Sebaliknya, mereka memutuskan untuk mengeluh bahwa situasi pengembang akan menjadi ambigu untuk mentransfer kontrol finally block.

Jadi itu adalah tugas pengembang untuk menyatakan dengan jelas apa yang dia ingin lakukan daripada mengkompilasi asumsi sesuatu yang lain.

Saya harap Anda mengerti mengapa ini tidak bisa dikompilasi!

Sriram Sakthivel
sumber
Pada catatan terkait, bahkan jika tidak ada "catch", jalur eksekusi setelah finallypernyataan akan dipengaruhi oleh apakah catchtelah keluar melalui pengecualian, dan tidak ada mekanisme untuk mengatakan bagaimana seharusnya berinteraksi dengan continue. Saya suka teladan Anda, karena ini menunjukkan masalah yang lebih besar.
supercat
@supercat Setuju dengan Anda, jawaban saya menunjukkan contoh situasi yang ambigu untuk compiler masih ada begitu banyak masalah dengan pendekatan ini.
Sriram Sakthivel
16

Seperti yang dinyatakan orang lain, tetapi berfokus pada pengecualian, ini benar-benar tentang penanganan pengalihan kendali yang ambigu.

Dalam benak Anda, Anda mungkin memikirkan skenario seperti ini:

public static object SafeMethod()
{
    foreach(var item in list)
    {
        try
        {
            try
            {
                //do something that won't transfer control outside
            }
            catch
            {
                //catch everything to not throw exceptions
            }
        }
        finally
        {
            if (someCondition)
                //no exception will be thrown, 
                //so theoretically this could work
                continue;
        }
    }

    return someValue;
}

Secara teoritis, Anda dapat melacak aliran kontrol dan berkata, ya, ini "ok". Tidak ada pengecualian yang dilemparkan, tidak ada kontrol yang ditransfer. Tapi desainer bahasa C # memiliki masalah lain dalam pikirannya.

Pengecualian yang Dilempar

public static void Exception()
{
    try
    {
        foreach(var item in list)
        {
            try
            {
                throw new Exception("What now?");
            }
            finally
            {
                continue;
            }
        }
    }
    catch
    {
        //do I get hit?
    }
}

The Dreaded Goto

public static void Goto()
{
    foreach(var item in list)
    {
        try
        {
            goto pigsfly;
        }
        finally
        {
            continue;
        }
    }

    pigsfly:
}

Kembalinya

public static object ReturnSomething()
{
    foreach(var item in list)
    {
        try
        {
            return item;
        }
        finally
        {
            continue;
        }
    }
}

Break up

public static void Break()
{
    foreach(var item in list)
    {
        try
        {
            break;
        }
        finally
        {
            continue;
        }
    }
}

Jadi kesimpulannya, ya, sementara ada adalah sebuah sedikit kemungkinan menggunakan continuedalam situasi di mana kontrol tidak ditransfer, tapi bagus (sebagian?) Dari kasus melibatkan pengecualian atau returnblok. Perancang bahasa merasa ini terlalu ambigu dan (kemungkinan) tidak mungkin untuk memastikan pada waktu kompilasi bahwa Anda hanyacontinue digunakan dalam kasus di mana aliran kontrol tidak ditransfer.

Chris Sinclair
sumber
Punya situasi yang sama di java ketika proguard mogok saat kebingungan. Penjelasan Anda cukup bagus. C # memberikan kesalahan waktu kompilasi - sangat masuk akal.
Dev_Vikram
11

Secara umum continuetidak masuk akal bila digunakan di finallyblok. Lihatlah ini:

foreach (var item in list)
{
    try
    {
        throw new Exception();
    }
    finally{
        //doesn't make sense as we are after exception
        continue;
    }
}
ghord
sumber
"Secara umum" banyak hal yang tidak masuk akal. Dan itu tidak berarti itu seharusnya tidak bekerja "secara khusus". IE: continuepernyataan loop luar tidak masuk akal, tetapi itu tidak berarti itu tidak didukung.
zerkms
Saya pikir itu masuk akal. itembisa jadi file, gagal baca -> finallytutup file. continuemencegah sisa pemrosesan.
jnovacho
5

"Ini tidak akan bisa dikompilasi dan saya pikir itu masuk akal"

Yah, menurutku tidak.

Ketika Anda benar-benar memilikinya catch(Exception)maka Anda tidak membutuhkan akhirnya (dan mungkin bahkan tidakcontinue ).

Ketika Anda memiliki yang lebih realistis catch(SomeException), apa yang harus terjadi jika pengecualian tidak tertangkap? Anda continueingin pergi ke satu arah, kecuali menangani yang lain.

Henk Holterman
sumber
2
Saya pikir Anda bisa membutuhkannya finallysaat Anda memilikinya catch. Ini biasanya digunakan untuk menutup sumber daya dengan cara yang andal.
jnovacho
Ini bukan hanya pengecualian. Anda dapat dengan mudah memiliki returnpernyataan di dalam blok tryatau Anda catch. Apa yang kita lakukan sekarang? Kembali atau lanjutkan pengulangan? (dan jika kita melanjutkan, bagaimana jika itu item terakhir untuk mengulang? Kemudian kita hanya melanjutkan di luar foreachdan kembali tidak pernah terjadi?) EDIT: Atau bahkan gotoke label di luar loop, hampir semua tindakan yang mentransfer kontrol di luar foreachloop .
Chris Sinclair
2
Saya tidak setuju dengan "Ketika Anda benar-benar memiliki tangkapan (Pengecualian) maka Anda tidak membutuhkan akhirnya (dan mungkin bahkan tidak melanjutkan).". Bagaimana jika saya perlu melakukan operasi apa pun pengecualian yang muncul atau tidak?
lpaloub
OK, akhirnya melayani tambahan returndi dalam blok. Tetapi biasanya eksekusi berlanjut setelah tangkapan semua.
Henk Holterman
'akhirnya' bukanlah 'menangkap'. Ini harus digunakan untuk kode pembersihan. blok terakhir berjalan terlepas dari apakah pengecualian dilemparkan atau tidak . Hal-hal seperti menutup file, atau mengosongkan memori (jika Anda menggunakan kode yang tidak terkelola) dll. Ini adalah hal-hal yang akhirnya Anda blok
Robotnik
3

Anda tidak dapat meninggalkan tubuh blok terakhir. Ini termasuk istirahat, kembali dan dalam kasus Anda melanjutkan kata kunci.

Joseph Devlin
sumber
3

The finallyblok dapat dijalankan dengan pengecualian menunggu untuk rethrown. Tidaklah masuk akal untuk bisa keluar dari blok (dengancontinue atau apa pun) tanpa memunculkan kembali pengecualian.

Jika Anda ingin melanjutkan pengulangan apa pun yang terjadi, Anda tidak memerlukan pernyataan akhirnya: Tangkap saja pengecualiannya dan jangan lempar ulang.

Falanwe
sumber
1

finallyberjalan baik pengecualian yang tidak tertangkap dilemparkan atau tidak. Orang lain telah menjelaskan mengapa ini membuat continuetidak masuk akal, tetapi berikut adalah alternatif yang mengikuti semangat dari apa yang tampaknya diminta oleh kode ini. Pada dasarnya, finally { continue; }mengatakan:

  1. Jika ada pengecualian yang tertangkap, lanjutkan
  2. Jika ada pengecualian yang tidak tertangkap, izinkan untuk dibuang, tetapi tetap lanjutkan

(1) dapat dipenuhi dengan menempatkan continuedi akhir masing-masing catch, dan (2) dapat dipenuhi dengan menyimpan pengecualian yang tidak tertangkap untuk dibuang nanti. Anda bisa menulisnya seperti ini:

var exceptions = new List<Exception>();
foreach (var foo in list) {
    try {
        // some code
    } catch (InvalidOperationException ex) {
        // handle specific exception
        continue;
    } catch (Exception ex) {
        exceptions.Add(ex);
        continue;
    }
    // some more code
}
if (exceptions.Any()) {
    throw new AggregateException(exceptions);
}

Sebenarnya finallymau juga dieksekusi dalam kasus ketiga, dimana tidak ada pengecualian dilempar sama sekali, tertangkap atau tidak tertangkap. Jika itu diinginkan, Anda tentu saja dapat menempatkan satu continuesetelah blok coba-tangkap alih-alih di dalam masing-masing catch.

nmclean
sumber
1

Secara teknis, ini adalah batasan dari CIL yang mendasarinya. Dari spesifikasi bahasa :

Transfer kontrol tidak pernah diizinkan untuk memasukkan penangan tangkapan atau klausa terakhir kecuali melalui mekanisme penanganan pengecualian.

dan

Transfer kontrol dari daerah yang dilindungi hanya diizinkan melalui instruksi pengecualian ( leave, end.filter, end.catch, atau end.finally)

Di halaman dokumen untuk brinstruksi :

Kontrol transfer masuk dan keluar dari percobaan, menangkap, menyaring, dan akhirnya blok tidak dapat dilakukan oleh instruksi ini.

Terakhir ini berlaku untuk semua instruksi cabang, termasuk beq, brfalse, dll

Tidak ditandatangani
sumber
-1

Para desainer bahasa hanya tidak ingin (atau tidak bisa) bernalar tentang semantik blok akhirnya diakhiri oleh transfer kontrol.

Satu masalah, atau mungkin masalah kuncinya, adalah bahwa file finally blok dijalankan sebagai bagian dari beberapa transfer kontrol non-lokal (pemrosesan pengecualian). Target dari transfer kontrol tersebut bukanlah loop yang melingkupi; pemrosesan pengecualian membatalkan perulangan dan melanjutkan penguliran lebih lanjut.

Jika kita memiliki transfer kontrol keluar dari finallyblok pembersihan, maka transfer kontrol asli sedang "dibajak". Itu dibatalkan, dan kontrol pergi ke tempat lain.

Semantik bisa dikerjakan. Bahasa lain memilikinya.

Para perancang C # memutuskan untuk melarang statis, transfer kendali "goto-like", dengan demikian menyederhanakan banyak hal.

Namun, bahkan jika Anda melakukan itu, itu tidak menyelesaikan pertanyaan tentang apa yang terjadi jika transfer dinamis dimulai dari finally: bagaimana jika blok terakhir memanggil fungsi, dan fungsi itu melempar? Proses pengecualian asli kemudian "dibajak".

Jika Anda memahami semantik bentuk pembajakan kedua ini, tidak ada alasan untuk menyingkirkan jenis pertama. Mereka benar-benar adalah hal yang sama: transfer kontrol adalah transfer kontrol, baik dalam lingkup leksikal yang sama atau tidak.

Kaz
sumber
Perbedaannya adalah bahwa finallymenurut definisi harus segera diikuti dengan melanjutkan transfer yang memicunya (pengecualian tidak tertangkap return, dll.). Akan mudah untuk mengizinkan transfer "seperti goto" dalam finally(bahasa mana yang melakukan ini?), Tetapi melakukannya berarti itu try { return } finally { ... } tidak akan kembali , yang sama sekali tidak terduga. Jika finallymemanggil fungsi yang melempar, itu sebenarnya bukan hal yang sama, karena kita sudah mengharapkan pengecualian dapat terjadi kapan saja dan mengganggu aliran normal.
nmclean
@nmclean: Sebenarnya, pengecualian yang lolos finallyadalah hal yang sama, karena dapat mengakibatkan aliran program yang tidak terduga dan tidak logis. Satu-satunya perbedaan adalah bahwa perancang bahasa, karena tidak dapat menolak semua program di mana blok terakhir dapat mengeluarkan pengecualian yang tidak tertangani, sebaliknya mengizinkan program tersebut untuk dikompilasi dan berharap program dapat menerima konsekuensi apa pun yang mungkin mengikuti pengabaian pengecualian-pembatalan sebelumnya urutan.
supercat
@supercat Saya setuju bahwa itu merusak aliran yang diharapkan, tetapi maksud saya adalah bahwa itu selalu terjadi untuk pengecualian yang tidak tertangani, apakah aliran saat ini adalah finallyatau tidak. Menurut logika paragraf terakhir Kaz, fungsi juga harus bebas untuk memengaruhi aliran dalam lingkup pemanggilnya dengan cara continue, dll., Karena mereka diizinkan melakukannya melalui pengecualian. Tapi ini tentu saja tidak diperbolehkan; pengecualian adalah satu - satunya pengecualian untuk aturan ini, oleh karena itu dinamakan "pengecualian" (atau "interupsi").
nmclean
@nmclean: Pengecualian tidak bersarang mewakili aliran kontrol yang berbeda dari eksekusi normal, tetapi masih terstruktur. Pernyataan yang mengikuti blok seharusnya hanya dijalankan jika semua pengecualian yang terjadi di dalam blok itu tertangkap di dalamnya. Itu mungkin tampak seperti ekspektasi yang masuk akal, tetapi pengecualian yang terjadi dalam satu finallyblok dapat melanggarnya. Benar-benar situasi yang buruk, yang IMHO seharusnya diselesaikan dengan setidaknya membiarkan finallyblok tahu tentang pengecualian yang tertunda sehingga mereka dapat membangun objek pengecualian komposit.
supercat
@mmclean Maaf, saya tidak setuju bahwa nama "pengecualian" berasal dari beberapa "pengecualian untuk aturan" (mengenai kontrol) khusus untuk C #, LOL. Aspek pengubah aliran kontrol dari suatu pengecualian hanyalah transfer kontrol dinamis non-lokal. Pengalihan kendali reguler dalam lingkup leksikal yang sama (tidak terjadi pelepasan) dapat dianggap sebagai kasus khusus dari itu.
Kaz