Getiri getirisi neden yakalamalı bir try bloğunun içinde görünmüyor?


97

Aşağıdakiler tamamdır:

try
{
    Console.WriteLine("Before");

    yield return 1;

    Console.WriteLine("After");
}
finally
{
    Console.WriteLine("Done");
}

finallyBlok şeyi yürütme tamamlandığında (ishal IEnumerator<T>destekleri IDisposabletamamlanmadan önce numaralandırma terk dahi Bunu sağlamak için bir yol sağlamak için).

Ama bu doğru değil:

try
{
    Console.WriteLine("Before");

    yield return 1;  // error CS1626: Cannot yield a value in the body of a try block with a catch clause

    Console.WriteLine("After");
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}

Deneme WriteLinebloğu içindeki çağrılardan biri veya daha fazlası tarafından bir istisna atıldığını varsayalım (argüman uğruna) . Yürütmeye catchblok halinde devam etmenin sorunu nedir ?

Elbette, getiri getirisi kısmı (şu anda) hiçbir şey atamıyor, ancak bu, neden bir kapsama sahip olmamızı try/ catcha'dan önce veya sonra atılan istisnalarla uğraşmamızı engellesin yield return?

Güncelleme: Burada Eric Lippert'ten ilginç bir yorum var - görünüşe göre dene / nihayet davranışını doğru bir şekilde uygulamak için yeterince sorun yaşıyorlar!

DÜZENLEME: Bu hatayla ilgili MSDN sayfası: http://msdn.microsoft.com/en-us/library/cs1x15az.aspx . Yine de nedenini açıklamıyor.



not: catch bloğunun kendisinde de teslim olamazsınız :-(
Simon_Weaver

2
Eski yeni şeyler bağlantısı artık çalışmıyor.
Sebastian Redl

Yanıtlar:


50

Bunun uygulanabilirlikten çok pratiklik meselesi olduğundan şüpheleniyorum. Sanırım bu kısıtlamanın aslında çözülemeyen bir sorun olduğu çok, çok az zaman var - ancak derleyicideki ek karmaşıklık çok önemli olacaktır.

Daha önce karşılaştığım buna benzer birkaç şey var:

  • Genel olamayan öznitelikler
  • X'in XY'den türetilememesi (X'te iç içe geçmiş bir sınıf)
  • Oluşturulan sınıflarda ortak alanları kullanan yineleyici blokları

Bu durumların her birinde, derleyicideki ekstra karmaşıklık pahasına, biraz daha fazla özgürlük elde etmek mümkün olacaktır. Ekip, onları alkışladığım pragmatik seçimi yaptı -% 99,9 doğru derleyici ile biraz daha kısıtlayıcı bir dil kullanmayı tercih ederim (evet, hatalar var; diğer gün SO'da biriyle karşılaştım) daha fazlası doğru derlenemeyen esnek dil.

DÜZENLEME: İşte neden mümkün olduğunun sahte bir kanıtı.

Bunu bir düşün:

  • Getiri dönüş bölümünün kendisinin bir istisna oluşturmadığından emin olabilirsiniz (değeri önceden hesaplayın ve sonra yalnızca bir alan ayarlayıp "doğru" döndürüyorsunuz)
  • Yineleyici bloğunda getiri getirisi kullanmayan deneme / yakalamaya izin verilir.
  • Yineleyici bloğundaki tüm yerel değişkenler, üretilen türdeki örnek değişkenlerdir, böylece kodu yeni yöntemlere özgürce taşıyabilirsiniz.

Şimdi dönüştürün:

try
{
    Console.WriteLine("a");
    yield return 10;
    Console.WriteLine("b");
}
catch (Something e)
{
    Console.WriteLine("Catch block");
}
Console.WriteLine("Post");

içine (bir tür sözde kod):

case just_before_try_state:
    try
    {
        Console.WriteLine("a");
    }
    catch (Something e)
    {
        CatchBlock();
        goto case post;
    }
    __current = 10;
    return true;

case just_after_yield_return:
    try
    {
        Console.WriteLine("b");
    }
    catch (Something e)
    {
        CatchBlock();
    }
    goto case post;

case post;
    Console.WriteLine("Post");


void CatchBlock()
{
    Console.WriteLine("Catch block");
}

Tek çoğaltma, dene / yakala blokları oluşturmaktır - ancak bu, derleyicinin kesinlikle yapabileceği bir şeydir.

Burada bir şeyi gözden kaçırmış olabilirim - eğer öyleyse, lütfen bana bildirin!


11
İyi bir kavram kanıtı, ancak bu strateji acı verici hale geliyor (muhtemelen bir C # programcısı için bir C # derleyici yazıcısına göre daha fazla) usingve gibi şeylerle kapsamlar oluşturmaya başladığınızda foreach. Örneğin:try{foreach (string s in c){yield return s;}}catch(Exception){}
Brian

Normal "dene / yakala" semantiği, dene / yakala bloğunun herhangi bir bölümü bir istisna nedeniyle atlanırsa, kontrolün, varsa uygun bir "yakalama" bloğuna aktarılacağı anlamına gelir. Ne yazık ki, bir getiri iadesi "sırasında" bir istisna meydana gelirse, yineleyicinin, bir istisna nedeniyle Elden Çıkarıldığı durumları, sahibinin ilgili tüm verileri aldığı için Elden Çıkarıldığı durumlardan ayırt etmesi mümkün değildir.
supercat

7
"Bu kısıtlamanın aslında çözülemeyecek bir sorun olduğu çok, çok az kez olduğundan şüpheleniyorum" Bu, istisnalara ihtiyacınız olmadığını söylemek gibi bir şey çünkü C'de yaygın olarak kullanılan hata kodu döndürme stratejisini kullanabilirsiniz. yıllar önce. Teknik zorlukların önemli olabileceğini kabul ediyorum, ancak bu yield, bence etrafından dolaşmak için yazmanız gereken spagetti kodu nedeniyle yararlılığını ciddi şekilde sınırlıyor .
jpmc26

@ jpmc26: Hayır, gerçekten bunu söylemek gibi değil. Bunu hatırlamıyorum hiç beni ısırma ve kaç kez yineleyici blokları bol kullandım. Bu biraz faydasını sınırlar yieldIMO - bu uzun bir yol var ciddi .
Jon Skeet

2
Bu 'özellik' aslında bazı durumlarda geçici çözüm için oldukça çirkin bir kod gerektirir, bkz. Stackoverflow.com/questions/5067188/…
namey

5

Bütün yieldbir yineleyici tanımında ifadeleri etkin bir şekilde kullanan bir devlet makinesinin bir hale dönüşürken switchavans devletlere deyimi. O Eğer yaptığımız için kodu oluşturmak yieldbir try / catch ifadeleri o çoğaltmak gerekir şeyi içinde tryiçin bloğun her yield her hariç ederken deyimi yieldbu blok için açıklama. Bu her zaman mümkün değildir, özellikle de bir yieldifade öncekine bağlıysa .


2
Ben buna inanmıyorum. Sanırım tamamen uygulanabilir - ama çok karmaşık.
Jon Skeet

2
C # 'teki dene / yakala blokları yeniden giriş anlamına gelmez. Bunları bölerseniz, bir istisnadan sonra MoveNext () 'i çağırmak ve muhtemelen geçersiz bir durumla try bloğuna devam etmek mümkündür.
Mark Cidade

2

Bir numaralandırıcıdan geri dönüş sağladığınızda çağrı yığınının sarılma / çözülme şekli nedeniyle, bir dene / yakala bloğunun istisnayı gerçekten "yakalamasının" imkansız hale geldiğini tahmin ediyorum. (çünkü yineleme bloğunu başlatmış olmasına rağmen getiri dönüş bloğu yığında değildir)

Bahsettiğim şey hakkında bir fikir edinmek için bir yineleyici bloğu ve bu yineleyiciyi kullanarak bir foreach kurun. Call Stack'in foreach bloğunun içinde nasıl göründüğünü kontrol edin ve ardından yineleyici dene / nihayet bloğunun içinde kontrol edin.


Kapsam dışı kalan yerel nesnelerde yıkıcıların çağrıldığı C ++ 'da yığın çözülmeye aşinayım. C # 'da karşılık gelen şey try / nihayet olacaktır. Ancak verim geri dönüşü olduğunda bu çözülme gerçekleşmez. Ve dene / yakala için getiri getirisi ile etkileşime girmesine gerek yoktur.
Daniel Earwicker

Bir yineleyici üzerinden döngü yaparken çağrı yığınına ne olduğunu kontrol edin ve ne demek istediğimi anlayacaksınız
Radu094

@ Radu094: Hayır, mümkün olacağına eminim. En azından biraz benzer olan nihayet halihazırda işlediğini unutmayın.
Jon Skeet

2

THE INVINCIBLE SKEET'in cevabını, Microsoft'tan biri gelip fikre soğuk su dökene kadar kabul ettim. Ama fikir meselesi kısmına katılmıyorum - tabii ki doğru bir derleyici tam olandan daha önemlidir, ancak C # derleyicisi bu dönüşümü bizim için olduğu kadar çözmede zaten çok akıllı. Bu durumda biraz daha eksiksizlik, dilin kullanımını, öğretmesini, açıklamasını daha az uç durum veya sorunla daha kolay hale getirecektir. Bu yüzden fazladan çabaya değeceğini düşünüyorum. Redmond'daki birkaç adam iki hafta boyunca kafalarını kaşıyor ve sonuç olarak önümüzdeki on yıl içinde milyonlarca kodlayıcı biraz daha rahatlayabilir.

(Ayrıca yield return, yinelemeyi yönlendiren kod tarafından devlet makinesine "dışarıdan" doldurulmuş bir istisnayı atmanın bir yolu olması için kötü bir arzu besliyorum. Ama bunu isteme nedenlerim oldukça belirsiz.)

Aslında Jon'un cevabı hakkında sahip olduğum bir sorgu, getiri dönüş ifadesi fırlatma ile ilgili.

Açıkçası getiri getirisi 10 o kadar kötü değil. Ama bu kötü olur:

yield return File.ReadAllText("c:\\missing.txt").Length;

Öyleyse, bunu önceki dene / yakala bloğunun içinde değerlendirmek daha mantıklı olmaz mıydı:

case just_before_try_state:
    try
    {
        Console.WriteLine("a");
        __current = File.ReadAllText("c:\\missing.txt").Length;
    }
    catch (Something e)
    {
        CatchBlock();
        goto case post;
    }
    return true;

Bir sonraki sorun, yuvalanmış deneme / yakalama blokları ve yeniden atma istisnaları olacaktır:

try
{
    Console.WriteLine("x");

    try
    {
        Console.WriteLine("a");
        yield return 10;
        Console.WriteLine("b");
    }
    catch (Something e)
    {
        Console.WriteLine("y");

        if ((DateTime.Now.Second % 2) == 0)
            throw;
    }
}
catch (Something e)
{
    Console.WriteLine("Catch block");
}
Console.WriteLine("Post");

Ama eminim mümkün ...


1
Evet, değerlendirmeyi dene / yakala'ya koyarsın. Değişken ayarını nereye koyduğunuz gerçekten önemli değil. Ana nokta, tek bir dene / yakala ile getiri getirisi, aralarında getiri olan iki deneme / yakalamaya etkili bir şekilde ayırabilmenizdir.
Jon Skeet
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.