Neden bir "devam" ifadesi "nihayet" bloğunun içinde olamaz?


107

Benim bir sorunum yok; Ben sadece merak ediyorum. Aşağıdaki senaryoyu hayal edin:

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

Bu derleyici hatası CS0157'ye neden olduğu için derlenmez :

Kontrol, bir nihayet maddesinin gövdesini terk edemez

Neden?


7
Yani. Sadece merak. Neden derlemeyeceği size tamamen mantıklı geliyorsa, neden birinin zaten mantıklı olanı açıklamasını istiyorsunuz? =)
J. Steen

14
Neden bir gerekir continue;içinde finallybloğun? blok continue;sonrasındaki ile aynı değil try -- catchmi?
bansi

6
@ J.Steen Hayır, finally/continueC # derleyicisinin bir sınırlaması olduğunu BİLİYORUM :-) Bu sınırlamanın nedenini ben de merak ediyorum.
xanatos

5
@xanatos - Teknik olarak konuşursak, temelde yatan CIL'in bir sınırlamasıdır. Gönderen dil spec : "Denetim transferi istisna işleme mekanizması yoluyla dışında bir catch işleyici veya nihayet maddesini girmesine izin verilmemelidir." ve "Korunan bir bölgeden kontrol aktarımına yalnızca bir istisna talimatı (ayrılma, end.filter, end.catch veya end.finally) yoluyla izin verilir." brŞube talimatlarının aile bu başaramayız.
imzalanmamış

1
@Unsigned: Bu da iyi bir sınırlama, tuhaf olmasına izin vermek :)
user7116

Yanıtlar:


150

finallybloklar bir istisna atılsa da atılmasa da çalışır. Bir istisna atılırsa, ne olur continueki? Döngünün yürütülmesine devam edemezsiniz çünkü yakalanmamış bir istisna denetimi başka bir işleve aktarır.

Bir istisna atılmasa bile finally, dene / yakala bloğu içindeki diğer kontrol aktarım ifadeleri çalıştığında çalışacaktır, returnörneğin, aynı sorunu getiren a gibi .

Kısacası, semantiği ile finallykontrolün bir finallybloğun içinden dışarıya aktarılmasına izin vermek mantıklı değil.

Bunu bazı alternatif anlambilimlerle desteklemek yardımcı olmaktan çok kafa karıştırıcı olacaktır çünkü amaçlanan davranışı daha net hale getiren basit geçici çözümler vardır. Böylece bir hata alırsınız ve sorununuz hakkında doğru şekilde düşünmeye zorlanırsınız. C # 'da devam eden genel "sizi başarı çukuruna atma" fikri.

C #, sen ve eğer başarılı olursa

İstisnaları yok saymak (çoğu zaman kötü bir fikirdir) ve döngüyü yürütmeye devam etmek istiyorsanız, bir tümünü yakala bloğu kullanın:

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

continueSadece yakalanmamış istisnalar atılmadığında yapmak istiyorsanız , sadece continuetry bloğunun dışına koyun .


12
Bunu yanıt olarak kabul edeceğim, çünkü Microsoft'un devam etmeyi kabul etmemeye karar vermesinin nedenlerini nihayetinde anlıyorsunuz. Muhtemelen bu görüntüler beni de ikna etti :)
lpaloub

20
"Sizi başarı çukuruna atma" fikrini detaylandırabilir misiniz? Anlayamadım :-D
Ant


13
Görüntü Jon Skeet, btw tarafından yapıldı. Buradan geldim: msmvps.com/blogs/jon_skeet/archive/2010/09/02/…
R. Martinho Fernandes

1
Elbette, bir bloktaki a continue, finallyyalnızca finallyblok içinde tanımlanan yerel bir döngüye devam ederse tamam olacaktır . Ancak soruda "dış" döngüyü sürdürmeye çalışır. Bir finallybloğun içinde sahip olamayacağınız benzer ifadeler return, break(bloğun dışına çıkarken) ve goto( finallybloğun dışındaki bir etikete giderken ). İlgili bir Java tartışması için bkz . Java'da bir nihayet blokundan geri dönme .
Jeppe Stig Nielsen

32

İşte güvenilir bir kaynak:

Bir continue ifadesi bir nihayet blokundan çıkamaz (Bölüm 8.10). Bir latest bloğu içinde bir continue ifadesi gerçekleştiğinde, continue ifadesinin hedefi aynı final bloğu içinde olmalıdır; aksi takdirde derleme zamanı hatası oluşur.

MSDN, 8.9.2 Continue deyiminden alınmıştır .

Belgeler şunu söylüyor:

Nihayet bloğunun ifadeleri, kontrol bir try ifadesinden çıktığında her zaman yürütülür. Bu, kontrol aktarımının normal yürütmenin bir sonucu olarak, bir break, continue, goto veya return ifadesinin yürütülmesinin bir sonucu olarak veya try ifadesinden bir istisnanın yayılmasının bir sonucu olarak meydana gelmesi durumunda geçerlidir. Bir nihayet bloğunun yürütülmesi sırasında bir istisna atılırsa, istisna sonraki çevreleyen try deyimine yayılır. Yayılma sürecinde başka bir istisna varsa, bu istisna kaybolur. Bir istisnayı yayma süreci, fırlatma bildiriminin açıklamasında (Bölüm 8.9.5) daha ayrıntılı olarak tartışılmıştır.

Buradan 8.10 try ifadesi .


31

Mantıklı olduğunu düşünebilirsiniz, ancak aslında mantıklı değil .

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

Bir istisna oluştuğunda ne ara vermeyi veya devam etmeyi düşünüyorsunuz ? C # derleyici ekibi, breakveya varsayarak kendi başına karar vermek istemez continue. Bunun yerine, geliştiricinin durumunun kontrolün aktarılmasının belirsiz olacağından şikayet etmeye karar verdiler finally block.

Bu nedenle, derleyicinin başka bir şeyi varsaymaktansa ne yapmak istediğini açıkça belirtmek geliştiricinin görevidir.

Umarım bunun neden derlenmediğini anlarsınız!


İlgili bir notta, "yakalama" olmasa bile, bir finallyifadeyi izleyen yürütme yolu catch, istisna yoluyla çıkıp çıkmadığından etkilenecektir ve bunun bir ile nasıl etkileşime girmesi gerektiğini söyleyen bir mekanizma yoktur continue. Yine de örneğinizi beğendim, çünkü daha da büyük bir problemi gösteriyor.
supercat

@supercat Sizinle aynı fikirdeyim, cevabım derleyici için belirsiz bir durum örneğini gösteriyor, hala bu yaklaşımla ilgili çok fazla sorun var.
Sriram Sakthivel

16

Diğerlerinin belirttiği gibi, ancak istisnalara odaklandı, bu gerçekten kontrolün aktarılmasının belirsiz bir şekilde ele alınmasıyla ilgili.

Aklınızda, muhtemelen böyle bir senaryo düşünüyorsunuz:

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

Teorik olarak, kontrol akışını izleyebilir ve evet, bu "tamam" diyebilirsiniz. Hiçbir istisna atılmaz, kontrol aktarılmaz. Ancak C # dili tasarımcılarının akıllarında başka sorunlar vardı.

Atılan İstisna

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

Korkunç Goto

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

    pigsfly:
}

Geri dönüş

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

Ayrılık

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

Orada iken Yani sonuç olarak, evet, olan bir hafif bir kullanma imkanı continuekontrolü aktarılıyor olmadığı durumlarda, ama iyi bir anlaşma (çoğunluk?) Vakalarının istisnalar dahil olduğu veya returnbloklar. Dil tasarımcıları bunun çok belirsiz olduğunu ve derleme sırasında sizin yalnızca kontrol akışının aktarılmadığı durumlarda continuekullanılmasını sağlamanın (muhtemelen) imkansız olacağını düşündüler .


Proguard gizlenirken çöktüğünde java'da aynı durumu elde etti. Açıklamanız oldukça iyi. C # derleme zamanı hatası veriyor - tamamen mantıklı.
Dev_Vikram

11

Genelde blokta continuekullanıldığında mantıklı değildir finally. Şuna bir bak:

foreach (var item in list)
{
    try
    {
        throw new Exception();
    }
    finally{
        //doesn't make sense as we are after exception
        continue;
    }
}

"Genel olarak" birçok şey mantıklı değil. Ve "özellikle" çalışmaması gerektiği anlamına gelmez. IE: continuedış döngü deyimi anlamsızdır, ancak desteklenmediği anlamına gelmez.
zerkms

Sanırım biraz mantıklı. itemdosya olabilir, okuma başarısız -> finallydosyayı kapatır. continueişlemin geri kalanını engeller.
jnovacho

5

"Bu derlenmeyecek ve bence tamamen mantıklı"

Ben öyle olmadığını düşünüyorum.

Kelimenin tam anlamıyla sahip catch(Exception)olduğunuzda, nihayet (ve muhtemelen bile continue) ihtiyacınız yoktur.

Daha gerçekçi catch(SomeException)olana sahip olduğunuzda, bir istisna yakalanmadığında ne olmalıdır? Kişisel continueistekleri bir şekilde, başka bir işleme istisna gitmek.


2
Sanırım finallyvarken ihtiyacın olabilir catch. Genellikle kaynakları güvenilir bir şekilde kapatmak için kullanılır.
jnovacho

Yine de sadece istisnalar değil. Kendi veya bloklarınızın içinde kolayca bir returnifadeye sahip olabilirsiniz . Şimdi ne yapacağız? Döngüye geri dönmek mi yoksa devam etmek mi? (ve devam edersek, ya yinelenen son öğe ise? O zaman sadece dışında devam ederiz ve dönüş asla olmaz?) DÜZENLEME: Döngünün dışındaki bir etikete bile , kontrolü döngünün dışına aktaran hemen hemen her eylem . trycatchforeachgotoforeach
Chris Sinclair 13

2
"Kelimenin tam anlamıyla yakaladığınızda (İstisna), o zaman sonunda (ve muhtemelen devamına bile) ihtiyacınız yok." Ya bir istisna ortaya çıkarsa çıksın ya da yaratmasa da bir operasyon yapmam gerekirse?
lpaloub 13

Tamam, sonunda returnblok içinde ek e- postalar için hizmet veriyor . Ancak normalde yürütme her şeyi yakalamadan sonra devam eder.
Henk Holterman

"nihayet", "yakalamak" değildir. Temizleme kodu için kullanılmalıdır. bir nihayet blok , bir istisna atılıp atılmadığına bakılmaksızın çalışır . Dosyaları kapatmak veya belleği serbest bırakmak (yönetilmeyen kod kullanıyorsanız) vb. Gibi şeyler, sonunda bloğa koyduğunuz şeylerdir
Robotnik 13

3

Nihayet bloğunun gövdesini terk edemezsiniz. Bu, ara, dönüş ve sizin durumunuzda devam anahtar kelimelerini içerir.


3

finallyBlok bir istisna rethrown olmak bekleyen çalıştırılabilir. continueİstisnayı yeniden atmadan bloktan ( veya başka bir şeyle) çıkabilmek gerçekten mantıklı olmaz .

Döngünüze ne olursa olsun devam etmek istiyorsanız, nihayet ifadesine ihtiyacınız yoktur: Sadece istisnayı yakalayın ve yeniden atmayın.


1

finallyyakalanmamış bir istisna atılıp atılmasa da çalışır. Başkaları bunun neden continuemantıksız olduğunu zaten açıkladılar , ancak burada bu kodun istediği şeyin ruhunu izleyen bir alternatif var. Temel finally { continue; }olarak şunu söylüyor:

  1. İstisnalar yakalandığında devam edin
  2. Yakalanmayan istisnalar olduğunda, bunların atılmasına izin verin, ancak yine de devam edin

(1) continueher birinin sonuna yerleştirilerek karşılanabilir catchve (2) daha sonra atılacak yakalanmamış istisnalar saklanarak karşılanabilir. Bunu şu şekilde yazabilirsin:

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

Aslında, finallyhiçbir istisnanın atılmadığı, yakalandığı veya yakalanmadığı üçüncü durumda da idam edilirdi. Bu istenirse, elbette continueher birinin içine değil de try-catch bloğunun arkasına bir tane yerleştirebilirsiniz catch.


1

Teknik olarak konuşursak, temelde yatan CIL'in bir sınırlamasıdır. Gönderen dil spec :

Kontrol aktarımının, istisna işleme mekanizması haricinde bir catch işleyicisine veya son cümlesine girmesine asla izin verilmez.

ve

Korunan bölgenin dışına Kontrol transferi sadece bir istisna öğretim yoluyla izin verilir ( leave, end.filter, end.catch, veya end.finally)

brTalimat için doküman sayfasında :

Try, catch, filter ve son olarak blokları içine ve dışına kontrol transferleri bu komutla gerçekleştirilemez.

Bu son için de geçerlidir tüm şube talimatlar da dahil beq, brfalsevb


-1

Dilin tasarımcıları, bir kontrol aktarımı ile sonlandırılan bir nihayet bloğunun anlambilimini basitçe düşünmek istemediler (veya edemediler).

Bir sorun veya belki de temel sorun, finallybloğun bazı yerel olmayan kontrol aktarımının (istisna işleme) bir parçası olarak yürütülmesidir. Bu kontrol aktarımının hedefi çevreleyen döngü değildir; istisna işleme, döngüyü iptal eder ve daha fazla çözülmeye devam eder.

finallyTemizleme bloğunun dışında bir kontrol transferimiz varsa, orijinal kontrol transferi "ele geçiriliyor" demektir. İptal edilir ve kontrol başka yere gider.

Anlambilim çözülebilir. Diğer dillerde var.

C # tasarımcıları statik, "goto benzeri" kontrol aktarımlarına izin vermemeye karar verdiler, böylece işleri biraz basitleştirdiler.

Bununla birlikte, bunu yapsanız bile, a'dan dinamik bir aktarım başlatılırsa ne olacağı sorusunu çözmez finally: ya nihayetinde blok bir işlevi çağırırsa ve bu işlev atarsa? Orijinal istisna işleme daha sonra "ele geçirilir".

Bu ikinci tür gasp etme biçiminin anlamını çözerseniz, birinci türü ortadan kaldırmak için hiçbir neden yoktur. Bunlar gerçekten aynı şeydir: bir kontrol transferi, aynı sözcük kapsamı olsun ya da olmasın, bir kontrol transferidir.


Aradaki fark, finallytanım gereği hemen ardından onu tetikleyen aktarıma devam edilmesi gerektiğidir (yakalanmamış bir istisna return, vb.). İçerisinde "goto benzeri" aktarımlara izin vermek kolay olacaktır finally(bu arada bunu hangi diller yapıyor?), Ancak bunu yapmak , tamamen beklenmedik bir şekilde try { return } finally { ... } geri dönmeyebileceği anlamına gelir . Eğer finallyçağrılar zaten istisnalar her zaman oluşur ve normal akışını kesintiye uğratabilecek bekliyoruz çünkü gerçekten aynı şey değil atar bir fonksiyon.
nmclean

@nmclean: Aslında, kaçan bir istisna finallyaynı şeydir, çünkü beklenmedik ve mantıksız program akışına neden olabilir. Tek fark, dil tasarımcılarının, nihai bir bloğun işlenmemiş bir istisna atabileceği tüm programları reddedememesi, bunun yerine bu tür programların derlenmesine izin vermesi ve programın önceki istisna çözme işleminin terk edilmesinin ardından olabilecek sonuçları kabul etmesini ummasıdır. sıra.
supercat

@supercat Beklenen akışı bozduğunu kabul ediyorum, ancak benim açımdan, mevcut akış bir olsun veya olmasın, işlenmemiş istisnalar için her zaman geçerli olmasıdır finally. Kaz'ın son paragrafının mantığına göre, işlevler continue, istisnalar yoluyla yapmalarına izin verildiği için, arayanın kapsamındaki akışı vb. Ancak buna elbette izin verilmiyor; istisnalar bu kuralın yegane istisnasıdır, dolayısıyla adı "istisna" (veya "kesinti").
nmclean

@nmclean: İç içe olmayan istisnalar, normal yürütmeden farklı, ancak yine de yapılandırılmış bir kontrol akışını temsil eder. Bir bloğu izleyen ifade, yalnızca bu blok içinde meydana gelen tüm istisnalar blok içinde yakalandığında yürütülmelidir. Bu makul bir beklenti gibi görünebilir, ancak bir finallyblok içinde meydana gelen bir istisna , onu ihlal edebilir. IMHO'nun en azından finallybloklara bekleyen istisnalar hakkında bilgi vererek kompozit istisna nesneleri oluşturabilmeleri için çözülmüş olması gereken gerçekten kötü bir durum .
supercat

@mmclean Üzgünüm, "istisna" adının, C #, LOL'ye özgü bazı "kuralın istisnasından" (kontrolle ilgili olarak) geldiğini kabul etmiyorum. Bir istisnanın kontrol akışını değiştiren yönü, sadece yerel olmayan dinamik kontrol transferidir. Aynı sözcük kapsamı içinde (çözülmenin olmaması) düzenli bir kontrol transferi, bunun özel bir durumu olarak kabul edilebilir.
Kaz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.