Berlabel break dan continue Pernyataan

Note

Artikel ini adalah spesifikasi fitur. Spesifikasi berfungsi sebagai dokumen desain untuk fitur tersebut. Ini termasuk perubahan spesifikasi yang diusulkan, bersama dengan informasi yang diperlukan selama desain dan pengembangan fitur. Artikel ini diterbitkan sampai perubahan spesifikasi yang diusulkan diselesaikan dan dimasukkan dalam spesifikasi ECMA saat ini.

Mungkin ada beberapa perbedaan antara spesifikasi fitur dan implementasi yang selesai. Perbedaan tersebut tertuangkan dalam catatan rapat terkait desain bahasa (LDM) .

Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .

RINGKASAN

Izinkan break dan pernyataan untuk secara opsional menentukan label yang mengidentifikasi perulangan atau switch pernyataan mana yang akan ditargetkan, memungkinkan alur kontrol yang lebih bersih dalam konstruksi berlapis tanpa memerlukan goto pernyataan, atau kontorsi lain seperti fungsi berlapis, pengembalian tuple, continue dll.

Motivasi

Saat bekerja dengan perulangan atau perulangan berlapis yang berisi switch pernyataan, pengembang sering kali perlu keluar dari atau melanjutkan perulangan luar dari dalam konteks dalam. Saat ini, ada dua pendekatan utama untuk mencapai hal ini, keduanya dengan kelemahan yang signifikan:

Menggunakan goto pernyataan

string foundValue = null;
for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        foundValue = GetValue(x, y);
        if (foundValue == target)
            goto FOUND;
    }
}
FOUND:
ProcessValue(foundValue);

Saat goto berfungsi, ini memerlukan penempatan label setelah konstruksi perulangan dan tidak secara jelas mengkomunikasikan niat untuk memutuskan dari perulangan tertentu. Untuk melanjutkan perulangan luar, pendekatannya menjadi lebih canggung:

for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        if (ShouldSkipRest(x, y))
            goto CONTINUE_OUTER;
    }
    CONTINUE_OUTER: ;
}

Pola ini membingungkan karena label harus ditempatkan di akhir bodi perulangan, tepat sebelum kurung kurawal penutup, sehingga inkrementor dan pemeriksaan kondisi terjadi. Ketika keduanya break dan continue diperlukan untuk perulangan luar yang sama, dua label terpisah diperlukan:

for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        if (ShouldSkipRest(x, y))
            goto CONTINUE_OUTER;
        
        if (ShouldExitAll(x, y))
            goto BREAK_OUTER;
    }
    CONTINUE_OUTER: ;
}
BREAK_OUTER:
// Subsequent statements

Menggunakan variabel bendera

string foundValue = null;
bool shouldBreak = false;
for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        foundValue = GetValue(x, y);
        if (foundValue == target)
        {
            shouldBreak = true;
            break;
        }
    }
    if (shouldBreak)
        break;
}
ProcessValue(foundValue);

Pendekatan ini memerlukan manajemen status tambahan, meningkatkan verbositas kode, dan mengaburkan niat alur kontrol.

Solusi yang diusulkan

Dengan berlabel break dan continue, kode menjadi lebih jelas dan lebih dapat dipertahankan:

string foundValue = null;
outer: for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        foundValue = GetValue(x, y);
        if (foundValue == target)
            break outer;
    }
}
ProcessValue(foundValue);

Label ditempatkan langsung pada perulangan yang diidentifikasinya, dan pernyataan break/continue secara eksplisit menamai targetnya. Untuk melanjutkan:

outer: for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        if (ShouldSkipRest(x, y))
            continue outer;
    }
}

Ini secara alami mengekspresikan "lanjutkan perulangan luar," tanpa kebingungan penempatan label yang terkait dengan goto. Label tunggal dapat digunakan untuk kedua operasi:

outer: for (int x = 0; x < xMax; x++)
{
    for (int y = 0; y < yMax; y++)
    {
        if (ShouldSkipRest(x, y))
            continue outer;
        
        if (ShouldExitAll(x, y))
            break outer;
    }
}

Fitur ini telah diminta secara ekstensif di komunitas C#, dengan diskusi yang berasal dari beberapa dekade terakhir dan topik yang diperkenalkan dan dipertanyakan kembali terus menerus. Fitur serupa ada dalam beberapa bahasa modern lainnya:

Dalam semua kasus ini, bahasa beroperasi dengan cara yang sama seperti dalam spesifikasi ini. Yaitu, beberapa konstruksi dapat memiliki label, dan dimungkinkan untuk mereferensikan label tersebut dari masing-masing continue atau break pernyataannya.

Desain terperinci

Pembaruan berikut disajikan sebagai perbedaan terhadap bagian yang sesuai dari standar C# 7 (statements.md). Di seluruh bagian ini, coretan menunjukkan teks dihapus dari spesifikasi yang ada, dan tebal menunjukkan teks ditambahkan. Prosa yang tidak berubah dikutip verbatim untuk konteks.

§13.5 Pernyataan berlabel

Sisipkan paragraf berikut segera setelah paragraf yang ada "Label dapat direferensikan dari goto pernyataan (§13.10.4) dalam cakupan label.":

Jika pernyataan segera disarangkan dalam labeled_statement adalah switch_statement (§13.8.3) atau iteration_statement (§13.9), pernyataan berlapis dikatakan diberi label denganpengidentifikasilabeled_statement. break_statement (§13.10.2) atau continue_statement (§13.10.3) dapat menentukan pengidentifikasi tersebut untuk mereferensikan pernyataan berlabel yang berisi.

Catatan: Hanya pernyataan yang segera ditumpuk dalam labeled_statement yang diberi label dengan pengidentifikasi tersebut. Misalnya, diberikan a: b: while (…) …, hanya b memberi label iteration_statement; a memberi label labeled_statementb: while (…) … dalam, yang bukan merupakan switch_statement atau iteration_statement. Akibatnya, break a; atau continue a; muncul dalam isi perulangan tidak menargetkan while pernyataan. catatan akhir

§13.10.2 Pernyataan pembobolan

break_statement
    : 'break' identifier? ';'
    ;

Pernyataan break keluar dari pernyataan penutup terdekat switch, , while, dofor, atau foreach .

Pernyataan break keluar dari switch_statement penutup terdekat (§13.8.3) atau iteration_statement (§13.9), atau, jika pengidentifikasi ditentukan, penutup terdekat switch_statement atau iteration_statement berlabel dengan pengidentifikasi tersebut (lihat §13,5).

Target break pernyataan adalah titik akhir dari pernyataan pernyataan penutup , , while, do, foratau foreach terdekatswitchyang ditentukan seperti di atas. break Jika pernyataan tidak diapit oleh switch, , while, do, foratau foreach pernyataan, kesalahan waktu kompilasi terjadi.Jika tidak ada pernyataan penutup seperti itu, kesalahan waktu kompilasi terjadi.

Ketika beberapa switchpernyataan , while, do, for, atau foreach disarangkan satu sama lain, break pernyataan hanya berlaku untuk pernyataan terdalu. Untuk mentransfer kontrol di beberapa tingkat bersarang, goto pernyataan (§13.10.4) harus digunakan.

Pernyataan break tidak dapat keluar dari finally blok (§13.11). break Ketika pernyataan terjadi dalam finally blok, target break pernyataan harus berada dalam blok yang samafinally; jika tidak, kesalahan waktu kompilasi terjadi.

Pernyataan break dijalankan sebagai berikut:

  • break Jika pernyataan keluar dari satu atau beberapa try blok dengan blok terkaitfinally, kontrol awalnya ditransfer ke finally blok pernyataan terdahulutry. Kapan dan jika kontrol mencapai titik finally akhir blok, kontrol ditransfer ke finally blok pernyataan penutup try berikutnya. Proses ini diulang sampai finally blok semua pernyataan intervensi try telah dijalankan.
  • Kontrol ditransfer ke target break pernyataan.

break Karena pernyataan secara tanpa syarat mentransfer kontrol di tempat lain, titik break akhir pernyataan tidak pernah dapat dijangkau.

Contoh: Berlabel break diselesaikan ke switch_statement penutup terdekat atau iteration_statement dengan label yang cocok:

outer: for (int i = 0; i < 10; i++)
{
    for (int j = 0; j < 10; j++)
    {
        if (i * j > 20)
            break outer; // exits the outer for-loop
    }
}

contoh akhir

§13.10.3 Pernyataan lanjutan

continue_statement
    : 'continue' identifier? ';'
    ;

Pernyataan memulai continue iterasi baru dari pernyataan penutup , , do, foratau foreach terdekatwhile.

Pernyataan memulai continue iterasi baru dari iteration_statement penutup terdekat (§13,9), atau, jika pengidentifikasi ditentukan, penutup terdekat iteration_statement berlabel dengan pengidentifikasi tersebut (lihat §13,5).

Target continue pernyataan adalah titik akhir dari pernyataan tersemat dari pernyataan terdekat yang mencakup while, , do, foratau foreach pernyataaniteration_statement ditentukan seperti di atas. continue Jika pernyataan tidak diapit oleh whilepernyataan , , do, foratau foreach , terjadi kesalahan waktu kompilasi.Jika tidak ada pernyataan penutup seperti itu, kesalahan waktu kompilasi terjadi.

Ketika beberapa whilepernyataan , do, for, atau foreach disarangkan satu sama lain, continue pernyataan hanya berlaku untuk pernyataan terdalu. Untuk mentransfer kontrol di beberapa tingkat bersarang, goto pernyataan (§13.10.4) harus digunakan.

Pernyataan continue tidak dapat keluar dari finally blok (§13.11). continue Ketika pernyataan terjadi dalam finally blok, target continue pernyataan harus berada dalam blok yang samafinally; jika tidak, kesalahan waktu kompilasi terjadi.

Pernyataan continue dijalankan sebagai berikut:

  • continue Jika pernyataan keluar dari satu atau beberapa try blok dengan blok terkaitfinally, kontrol awalnya ditransfer ke finally blok pernyataan terdahulutry. Kapan dan jika kontrol mencapai titik finally akhir blok, kontrol ditransfer ke finally blok pernyataan penutup try berikutnya. Proses ini diulang sampai finally blok semua pernyataan intervensi try telah dijalankan.
  • Kontrol ditransfer ke target continue pernyataan.

continue Karena pernyataan secara tanpa syarat mentransfer kontrol di tempat lain, titik continue akhir pernyataan tidak pernah dapat dijangkau.

Contoh: Berlabel continue diselesaikan ke iteration_statement penutup terdekat dengan label yang cocok:

outer: for (int i = 0; i < 10; i++)
{
    for (int j = 0; j < 10; j++)
    {
        if (ShouldSkip(i, j))
            continue outer; // continues the outer for-loop
    }
}

contoh akhir

Kelemahan/Alternatif

Tetap gunakan goto pernyataan

C# sudah mendukung goto, yang dapat mencapai alur kontrol yang sama. Namun, goto memiliki beberapa kelemahan dibandingkan dengan jeda berlabel/lanjutkan:

  • Memerlukan label terpisah untuk skenario break vs. continue (break labels go after the loop, continue labels go before the closing brace)
  • Penempatan label kurang intuitif dan berbeda berdasarkan apakah Anda melanggar atau melanjutkan
  • Kurang eksplisit tentang niat (melompat ke lokasi vs. melanggar/melanjutkan perulangan tertentu)
  • Rapuh dan rawan kesalahan: pengembang harus memastikan tidak ada pernyataan yang secara tidak sengaja ditempatkan antara label dan konstruksi targetnya. Misalnya, dengan goto END_LOOP; diikuti oleh END_LOOP:, mudah untuk secara tidak sengaja menyisipkan pernyataan di antara mereka selama pemeliharaan, merusak alur kontrol yang dimaksudkan. Perulangan berlabel mencegah masalah ini dengan mengikat label langsung ke konstruksi.
  • Membawa stigma historis yang berlabel istirahat/lanjutkan menghindari

Menggunakan variabel bendera

Seperti yang ditunjukkan di bagian motivasi, variabel bendera berfungsi tetapi menambahkan boilerplate yang signifikan dan mengaburkan logika alur kontrol.

Menggunakan break N atau continue N dengan tingkat numerik

  • Rapuh selama pemfaktoran ulang (menambahkan/menghapus tingkat perulangan memerlukan pembaruan semua referensi numerik)
  • Lebih sulit dibaca (harus menghitung tingkat untuk memahami target)
  • Kurang eksplisit daripada label bernama
  • Kurangnya kejelasan (berbasis 1? berbasis 0?)

Refaktor ke dalam metode terpisah

Meskipun ini sering kali merupakan praktik yang baik, ini tidak selalu layak atau tepat, dan kadang-kadang memperkenalkan kompleksitas yang tidak perlu untuk apa yang harus menjadi alur kontrol sederhana.

Proposal ini mengonsolidasikan dan membahas diskusi komunitas berikut:

Buka pertanyaan

semantik label

Spesifikasi saat ini mendefinisikan semantik untuk break identifier/continue identifier sebagai menemukan konstruksi perulangan/sakelar terdalam yang berlaku berlabel dengan pengidentifikasi tersebut, dan kemudian mengirimkannya dengan semantik standar break/continue . Formalisasi alternatif adalah mengatakan bahwa break identifier/continue identifier mengidentifikasi label menggunakan aturan yang sama dengan 'goto'. Dan jika label secara langsung berisi konstruksi perulangan/sakelar yang mengapit jeda/lanjutkan, maka itu adalah perulangan/sakelar tempat pemutus/lanjutkan berlaku.

Kedua formalisasi secara efektif identik, memungkinkan dan melarang serangkaian program yang sama. Pendekatan yang dipilih dalam speklet ini dilakukan untuk kesederhanaan konseptual dan harfiah. Ini tidak harus mencakup cakupan label, atau pengikatan pengidentifikasi seperti goto halnya, atau harus mendefinisikan logika pengikatan keluar untuk menyelesaikan pernyataan loop/switch dan lanjutkan/putuskan. Sebaliknya, ini hanya memperluas bahasa spesifikasi sederhana yang menemukan perulangan/sakelar penutup yang sesuai mengingat jeda/lanjutkan, memungkinkannya untuk memperluas melewati yang paling dalam, ke sesuatu di atas itu.

Jika LDM merasa mengikat semantik goto+label ini lebih ketat, tidak akan sulit untuk menyesuaikan spesifikasi dengan itu. Menjaga pertanyaan ini terbuka jika kelompok merasa bentuk terakhir lebih alami daripada formulir yang diambil di sini.

void M()
{
  label:
  Console.WriteLine();
  
  foreach (var x in ...)
  {
    break label;
    // should this scenario fail because:
    // 1. the identifier lookup fails, or
    // 2. the label is rejected (not a valid label for a `break` since not attached to a loop construct) after being found?
  }
}

Spesifikasi tentang bagaimana label dideklarasikan:

Setiap blok atau switch_block membuat ruang deklarasi terpisah untuk label. Nama diperkenalkan ke dalam ruang deklarasi ini melalui labeled_statements, dan nama dirujuk melalui goto_statements. Ruang deklarasi label blok mencakup blok berlapis. Dengan demikian, dalam blok berlapis tidak dimungkinkan untuk mendeklarasikan label dengan nama yang sama dengan label dalam blok penutup.

Spesifikasi pada pernyataan berlabel:

Cakupan label adalah seluruh blok tempat label dideklarasikan, termasuk blok berlapis apa pun. Ini adalah kesalahan waktu kompilasi untuk dua label dengan nama yang sama untuk memiliki cakupan yang tumpang tindih.
Label dapat dirujuk dari pernyataan goto (§13.10.4) dalam cakupan label.

label berlapis

Harus a: b: while (true) continue a; didukung?

Rekomendasi: tidak. Tidak ada pengguna yang meminta ini. Tidak ada kasus penggunaan yang menarik yang disajikan untuk itu. Sebagian besar bahasa mainstream tidak mengizinkannya, tanpa keluhan dari komunitas mereka. Bahasa gaul (dan impl) lebih sederhana dan lebih jelas membawa ketat bahwa hanya pernyataan berlabel langsung yang melabeli perulangan/sakelar.

Rapat desain

TBD