Etiketli break ve continue Deyimleri

Note

Bu makale bir özellik belirtimidir. Belirtim, özelliğin tasarım belgesi olarak görev alır. Önerilen belirtim değişikliklerini ve özelliğin tasarımı ve geliştirilmesi sırasında gereken bilgileri içerir. Bu makaleler, önerilen belirtim değişiklikleri son haline getirilene ve geçerli ECMA belirtimine dahil edilene kadar yayımlanır.

Özellik belirtimi ile tamamlanan uygulama arasında bazı tutarsızlıklar olabilir. Bu farklılıklar ilgili dil tasarım toplantısı (LDM) notlarında yakalanır.

Özellik belirtimlerini C# dil standardına benimseme işlemi hakkında daha fazla bilgi edinmek içinbelirtimleri makalesinde bulabilirsiniz.

Summary

İzin ver break ve continue deyimleri isteğe bağlı olarak hangi döngünün veya switch deyimin hedefleneceğini tanımlayan bir etiket belirterek deyim gerektirmeden goto iç içe geçmiş yapılarda daha temiz denetim akışına veya iç içe geçmiş işlevler, tanımlama grubu dönüşleri gibi diğer contortion'lara olanak tanır.

Motivasyon

İç içe döngüler veya deyimler içeren switch döngülerle çalışırken, geliştiricilerin genellikle iç bağlamın içinden dış döngüden ayrılmaları veya devam etmesi gerekir. Şu anda, bunu başarmak için iki birincil yaklaşım vardır ve her ikisi de önemli dezavantajları vardır:

Deyimleri kullanma goto

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

Çalışırken goto , döngü yapısından sonra etiket yerleştirmeyi gerektirir ve belirli bir döngüden ayrılma amacını açıkça iletmez. Dış döngünün devam etmesi için yaklaşım daha da garip hale gelir:

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

Bu düzen kafa karıştırıcıdır çünkü etiket, artımcı ve koşul denetiminin gerçekleşmesi için kapanış ayracından hemen önce döngü gövdesinin sonuna yerleştirilmelidir. Aynı dış döngü için hem hem continue de break gerektiğinde iki ayrı etiket gerekir:

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

Bayrak değişkenlerini kullanma

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

Bu yaklaşım ek durum yönetimi gerektirir, kod ayrıntı düzeyini artırır ve denetim akışı amacını gizler.

Önerilen çözüm

ve continueetiketli break kod daha net ve daha sürdürülebilir hale gelir:

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

Etiket doğrudan tanımlamış olduğu döngüye yerleştirilir ve break/continue deyimi açıkça hedefini adlandırmaktadır. Devam için:

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

Bu, ile gotoilişkili etiket yerleşiminin karışıklığı olmadan doğal olarak "dış döngüye devam et" ifadesini ifade eder. Her iki işlem için de tek bir etiket kullanılabilir:

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

Bu özellik C# topluluğunda, onlarca yıl öncesine dayanan tartışmalar ve konu sürekli yeniden tanıtılıp yeniden istenerek yoğun olarak istendi. Benzer özellikler diğer birkaç modern dilde de mevcuttur:

Tüm bu durumlarda, diller bu belirtimdekiyle aynı şekilde çalışır. Yani, bazı yapıların bir etiketi olabilir ve ilgili veya break deyimlerinden continue bu etikete başvurmak mümkündür.

Ayrıntılı tasarım

Aşağıdaki güncelleştirmeler, C# 7 standardının (statements.md) ilgili bölümlerine karşı fark olarak sunulur. Bu bölüm boyunca , üstü çizili , metnin mevcut belirtimden kaldırıldığını, kalın ise metnin eklendiğini gösterir. Bağlam için değişmemiş düz yazı alıntısı yapılır.

§13.5 Etiketli deyimler

Var olan "Etiket kapsamındaki deyimlerden goto (§13.10.4) bir etikete başvurulabilir" paragrafının hemen arkasına aşağıdaki paragrafı ekleyin:

Bir labeled_statement hemen iç içe yerleştirilmiş deyimi bir switch_statement (§13.8.3) veya bir iteration_statement (§13.9) ise, iç içe deyimin labeled_statementtanımlayıcısıyla etiketlendiği söylenir. bir break_statement (§13.10.2) veya continue_statement (§13.10.3) içeren etiketli deyime başvurmak için böyle bir tanımlayıcı belirtebilir.

Not: Yalnızca bir labeled_statementhemen iç içe yerleştirilmiş deyimi bu tanımlayıcıyla etiketlenmiştir. Örneğin, verilen a: b: while (…) …b yalnızca iteration_statement etiketler; a kendisi bir switch_statement veya iteration_statementb: while (…) … olmayan iç labeled_statement etiketler. Sonuç olarak, break a; döngü gövdesinde görünen veya continue a; deyimini while hedeflemez. dipnot

§13.10.2 Kesme deyimi

break_statement
    : 'break' identifier? ';'
    ;

deyimi break , en yakın kapsayan switch, while, do, forveya foreach deyiminden çıkar.

deyimibreak, en yakın kapsayan switch_statement (§13.8.3) veya iteration_statement (§13.9) veya tanımlayıcı belirtilirse, bu tanımlayıcıyla etiketlenmiş en yakın kapsayan switch_statement veya iteration_statement çıkar (bkz. §13.5).

Deyimin break hedefi, yukarıda belirtilen en yakın kapsayan switch, while, do, forveya foreach deyimideyiminin bitiş noktasıdır. Bir break deyimi , , while, doforveya foreach deyimiyle switchkapatılmazsa, derleme zamanı hatası oluşur.Böyle bir kapsayan deyim yoksa, derleme zamanı hatası oluşur.

Birden çok switch, while, do, , forveya foreach deyimi iç içe geçtiğinde, bir break deyim yalnızca en içteki deyim için geçerlidir. Denetimi birden çok iç içe geçme düzeyinde aktarmak için bir goto deyim (§13.10.4) kullanılmalıdır.

Bir break ifade bir finally bloktan (§13.11) çıkış yapamaz. Bir break deyim bir finally blok içinde gerçekleştiğinde, deyiminin break hedefi aynı finally blok içinde olmalıdır; aksi takdirde bir derleme zamanı hatası oluşur.

Bir break ifadesi aşağıdaki gibi yürütülür:

  • Eğer break deyimi, ilişkili try bloklarla bir veya daha fazla finally bloktan çıkarsa, denetim başlangıçta en içteki finally deyiminin try bloğuna aktarılır. Bir finally bloğunun bitiş noktasına ulaşıldığında ve bu gerçekleştiğinde, kontrol bir sonraki kapsayan finally deyiminin try bloğuna aktarılır. Bu işlem, tüm araya gelen finally ifadelerin try blokları yürütülene kadar yinelenir.
  • Denetim, break deyiminin hedefine aktarılır.

Bir break deyim, denetimi koşulsuz olarak başka bir yere aktardığından, deyiminin break bitiş noktasına hiçbir zaman ulaşılamaz.

Örnek: Etiketlenmiş break en yakın kapsayan switch_statement veya eşleşen etikete sahip iteration_statement çözümleniyor:

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

son örnek

§13.10.3 Continue deyimi

continue_statement
    : 'continue' identifier? ';'
    ;

deyimicontinue, en yakın kapsayan while, , doforveya foreach deyiminin yeni bir yinelemesini başlatır.

deyimicontinue, en yakın kapsayan iteration_statement (§13.9) yeni bir yineleme başlatır veya bir tanımlayıcı belirtilirse, bu tanımlayıcıyla etiketlenmiş en yakın kapsayan iteration_statement (bkz. §13.5).

Bir continue deyiminin hedefi, yukarıdaki gibi belirlenen en yakın kapsayan while, , doforveya foreachdeyiminin ekli deyiminin bitiş noktasıdır iteration_statement. Bir continue deyimi , , doforveya foreach deyimiyle whilekapatılmazsa, derleme zamanı hatası oluşur.Böyle bir kapsayan deyim yoksa, derleme zamanı hatası oluşur.

Birden çok while, do, forveya foreach deyimi birbirinin içine yerleştirildiğinde, bir continue deyim yalnızca en içteki deyime uygulanır. Denetimi birden çok iç içe geçme düzeyinde aktarmak için bir goto deyim (§13.10.4) kullanılmalıdır.

Bir continue ifade bir finally bloktan (§13.11) çıkış yapamaz. Bir continue deyim bir finally blok içinde gerçekleştiğinde, deyiminin continue hedefi aynı finally blok içinde olmalıdır; aksi takdirde bir derleme zamanı hatası oluşur.

Bir continue ifadesi aşağıdaki gibi yürütülür:

  • Eğer continue deyimi, ilişkili try bloklarla bir veya daha fazla finally bloktan çıkarsa, denetim başlangıçta en içteki finally deyiminin try bloğuna aktarılır. Bir finally bloğunun bitiş noktasına ulaşıldığında ve bu gerçekleştiğinde, kontrol bir sonraki kapsayan finally deyiminin try bloğuna aktarılır. Bu işlem, tüm araya gelen finally ifadelerin try blokları yürütülene kadar yinelenir.
  • Denetim, continue deyiminin hedefine aktarılır.

Bir continue deyim, denetimi koşulsuz olarak başka bir yere aktardığından, deyiminin continue bitiş noktasına hiçbir zaman ulaşılamaz.

Örnek: Etiketlenmiş continue , eşleşen etikete sahip en yakın kapsayan iteration_statement çözümleniyor:

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

son örnek

Dezavantajlar/Alternatifler

Deyimleri kullanmaya goto devam edin

C# zaten aynı denetim akışını gerçekleştirebilen öğesini destekler goto. Ancak, goto etiketli kesme/devam etme ile karşılaştırıldığında çeşitli dezavantajları vardır:

  • Kesme ve devam senaryoları için ayrı etiketler gerektirir (kesme etiketleri döngüden sonra gider, devam etiketleri kapanış ayracından önce gider)
  • Etiket yerleşimi daha az sezgiseldir ve kırılma veya devam etme durumlarına göre farklılık gösterir
  • Amaç hakkında daha az açık (bir konuma atlama ve belirli bir döngünün bozulması/devam etmesi)
  • Kırılgan ve hataya açık: Geliştiriciler etiketlerin ve hedef yapılarının arasına yanlışlıkla hiçbir deyimin yerleştirilmediğinden emin olmalıdır. Örneğin, goto END_LOOP; ve ardından END_LOOP:ile, bakım sırasında yanlışlıkla bunların arasına bir deyim eklemek ve hedeflenen denetim akışını bozmak kolaydır. Etiketli döngüler, etiketi doğrudan yapıya bağlayarak bu sorunu önler.
  • Kesme/devam et etiketli geçmiş damgayı taşır

Bayrak değişkenlerini kullanma

Motivasyon bölümünde gösterildiği gibi, bayrak değişkenleri çalışır ancak önemli bir ortak yapı ekler ve denetim akışı mantığını gizler.

Sayısal düzeylerle veya continue N kullanma break N

  • Yeniden düzenleme sırasında kırılganlık (döngü düzeyi eklemek/kaldırmak için tüm sayısal başvuruların güncelleştirilmesi gerekir)
  • Okuması daha zor (hedefi anlamak için düzeyleri saymalıdır)
  • Adlandırılmış etiketlerden daha az açık
  • Netlik eksikliği (1 tabanlı mı? 0 tabanlı mı?)

Ayrı yöntemler halinde yeniden düzenleme

Bu genellikle iyi bir uygulama olsa da, her zaman uygun veya uygun değildir ve bazen basit denetim akışı olması gerekenler için gereksiz karmaşıklık getirir.

Bu teklif aşağıdaki topluluk tartışmalarını birleştirir ve ele alır:

Açık sorular

etiket semantiği

Geçerli belirtim, için break identifier/continue identifier semantiği, bu tanımlayıcıyla etiketlenmiş en içteki geçerli döngü/anahtar yapısını bulma ve ardından standart break/continue semantik ile gönderme olarak tanımlar. Alternatif bir resmileştirme, bunun yerine 'goto' ile aynı kuralları kullanan bir etiketi tanımladığını break identifier/continue identifier söylemektir. Etiket doğrudan kesme/devam etme kapsayan bir döngü/anahtar yapısı içeriyorsa, kesme/devam etme işleminin geçerli olduğu döngü/anahtardır.

Her iki resmileştirme de etkili bir şekilde aynıdır ve aynı program kümesine izin verir ve izin vermez. Bu belirtim içinde seçilen yaklaşım hem kavramsal hem de değişmez basitlik için yapılmıştır. Etiketlerin kapsamını veya tanımlayıcıların bağlamasını olduğu gibi goto kapsaması ya da döngü/anahtar ve continue/break deyimini çözümlemek için dışa doğru bağlama mantığını tanımlaması gerekmez. Bunun yerine, kesme/devam etme işlemine göre uygun kapsayan döngüyü/anahtarı bulan basit belirtim dilini genişleterek en içtekini aşarak bunun üzerinde bir şeye genişletmesini sağlar.

LDM, goto+label semantiğine bu kadar sıkı bağlanıyorsa, belirtimi buna ayarlamak zor olmaz. Grup ikinci formun burada alınan formdan daha doğal olduğunu düşünüyorsa bu soruyu açık tutun.

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

Etiketlerin nasıl bildirildiğinde belirtim:

Her blok veya switch_block etiketler için ayrı bir bildirim alanı oluşturur. Adlar labeled_statements aracılığıyla bu bildirim alanına eklenir ve adlara goto_statements üzerinden başvurulur. Bir bloğun etiket bildirim alanı, iç içe blokları içerir. Bu nedenle, iç içe yerleştirilmiş bir bloğun içinde, kapsayan bir bloktaki etiketle aynı ada sahip bir etiket bildirmek mümkün değildir.

Etiketli deyimlerdeki belirtim:

Bir etiketin kapsamı, iç içe yerleştirilmiş bloklar da dahil olmak üzere etiketin bildirildiği bloğun tamamıdır. Aynı ada sahip iki etiketin çakışan kapsamlara sahip olması derleme zamanı hatasıdır.
Etiket kapsamındaki goto deyimlerinden (§13.10.4) bir etikete başvurulabilir.

iç içe etiketler

Desteklenmeli mi a: b: while (true) continue a; ?

Öneri: hayır. Hiçbir kullanıcı bunu istemedi. Bunun için ilgi çekici kullanım örnekleri sunulmadı. Çoğu temel dil, topluluklarından hiçbir şikayet olmadan buna izin vermez. Lang (ve impl) daha basit ve daha nettir ve yalnızca doğrudan içeren etiketli deyimin döngü/anahtarı etiketlediğini katılaştırır.

Tasarım toplantıları

TBD