Poniższe są w porządku:
try
{
Console.WriteLine("Before");
yield return 1;
Console.WriteLine("After");
}
finally
{
Console.WriteLine("Done");
}
finally
Blok pracuje, gdy cała sprawa zakończyła wykonywanie ( IEnumerator<T>
podpory IDisposable
, aby zapewnić sposób, aby zapewnić to nawet wtedy, gdy wyliczenie zostanie przerwany przed jej zakończeniem).
Ale to nie jest w porządku:
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);
}
Załóżmy (ze względu na argument), że jedno lub drugie WriteLine
wywołanie wewnątrz bloku try zgłasza wyjątek . Jaki jest problem z kontynuowaniem wykonywania w catch
bloku?
Oczywiście, część zwrotu z zysku nie jest (obecnie) w stanie niczego rzucić, ale dlaczego to powinno nas powstrzymywać od dołączania try
/ catch
zajmowania się wyjątkami rzucanymi przed lub po yield return
?
Aktualizacja: Jest tu ciekawy komentarz od Erica Lipperta - wydaje się, że mają już wystarczająco dużo problemów z poprawnym wdrożeniem zachowania try / final!
EDYTUJ: Strona MSDN dotycząca tego błędu to: http://msdn.microsoft.com/en-us/library/cs1x15az.aspx . Nie wyjaśnia to jednak, dlaczego.
źródło
Odpowiedzi:
Podejrzewam, że jest to kwestia praktyczności, a nie wykonalności. Podejrzewam, że są bardzo, bardzo kilka razy, gdzie ograniczenie to rzeczywiście problem, że nie można obejść - ale dodał złożoność kompilator byłoby bardzo istotne.
Jest kilka rzeczy, które już napotkałem:
W każdym z tych przypadków możliwe byłoby uzyskanie nieco większej swobody kosztem dodatkowej złożoności kompilatora. Zespół dokonał pragmatycznego wyboru, za co im oklaskiwam - wolałbym mieć nieco bardziej restrykcyjny język z kompilatorem dokładnym w 99,9% (tak, są błędy; natknąłem się na jeden z SO dopiero niedawno) niż bardziej elastyczny język, który nie mógł się poprawnie skompilować.
EDYCJA: Oto pseudo-dowód na to, dlaczego jest to wykonalne.
Weź pod uwagę, że:
Teraz przekształć:
try { Console.WriteLine("a"); yield return 10; Console.WriteLine("b"); } catch (Something e) { Console.WriteLine("Catch block"); } Console.WriteLine("Post");
into (rodzaj pseudokodu):
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"); }
Jedyne powielanie polega na ustawianiu bloków try / catch - ale kompilator z pewnością może to zrobić.
Mogłem coś tutaj przeoczyć - jeśli tak, daj mi znać!
źródło
using
iforeach
. Na przykład:try{foreach (string s in c){yield return s;}}catch(Exception){}
yield
, moim zdaniem, ze względu na kod spaghetti, który trzeba napisać, aby go obejść.yield
IMO - daleko do poważnych .Wszystkie
yield
instrukcje w definicji iteratora są konwertowane na stan w automacie stanowym, który efektywnie używaswitch
instrukcji do przechodzenia do kolejnych stanów. Jeśli to nie generuje kod dlayield
instrukcji w try / catch musiałby powielać wszystko wtry
bloku dla każdegoyield
rachunku z wykluczeniem wszelkich innychyield
instrukcji dla tego bloku. Nie zawsze jest to możliwe, szczególnie jeśli jednayield
instrukcja jest zależna od wcześniejszej.źródło
Spekulowałbym, że ze względu na sposób, w jaki stos wywołań jest nawijany / rozwijany, gdy zwracasz wynik z modułu wyliczającego, blok try / catch nie może faktycznie „złapać” wyjątku. (ponieważ bloku zwrotu zysku nie ma na stosie, mimo że zapoczątkował blok iteracji)
Aby uzyskać wyobrażenie o tym, o czym mówię, skonfiguruj blok iteratora i foreach używający tego iteratora. Sprawdź, jak wygląda stos wywołań wewnątrz bloku foreach, a następnie sprawdź go w iteratorze try / final block.
źródło
Zaakceptowałem odpowiedź THE INVINCIBLE SKEET, dopóki nie pojawi się ktoś z Microsoftu, by oblać ten pomysł zimną wodą. Ale nie zgadzam się z częścią opiniodawczą - oczywiście poprawny kompilator jest ważniejszy niż kompletny, ale kompilator C # jest już bardzo sprytny, jeśli chodzi o uporządkowanie tej transformacji dla nas, o ile to robi. Nieco większa kompletność w tym przypadku ułatwiłaby używanie, nauczanie, wyjaśnianie języka, z mniejszą liczbą skrajnych przypadków lub problemów. Więc myślę, że byłoby to warte dodatkowego wysiłku. Kilku facetów z Redmond drapie się po głowach przez dwa tygodnie, w wyniku czego miliony programistów w ciągu następnej dekady mogą się trochę bardziej zrelaksować.
(Mam również podłe pragnienie, aby istniał sposób na zrobienie
yield return
wyjątku rzutowania, który został wepchnięty do automatu stanowego „z zewnątrz” przez kod sterujący iteracją. Ale powody, dla których chcę tego, są dość niejasne).Właściwie jedno pytanie, które mam na temat odpowiedzi Jona, dotyczy wyrzucania wyrażenia zwrotu zysku.
Oczywiście zwrot z zysku 10 nie jest taki zły. Ale to byłoby złe:
yield return File.ReadAllText("c:\\missing.txt").Length;
Czy więc nie miałoby sensu oceniać tego w poprzedzającym bloku try / catch:
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;
Następnym problemem byłyby zagnieżdżone bloki try / catch i wyjątki ponownego odrzucenia:
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");
Ale jestem pewien, że to możliwe ...
źródło