Dlaczego yield return nie może pojawić się wewnątrz bloku próbnego z haczykiem?

Jest ok:

try
{
    Console.WriteLine("Before");

    yield return 1;

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

Blok finally uruchamia się po zakończeniu wykonywania całości (IEnumerator<T> obsługuje IDisposable, aby zapewnić sposób, aby to zapewnić, nawet jeśli wyliczenie zostanie przerwane przed jego 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 (na potrzeby argumentu), że wyjątek jest wyrzucany przez jedno lub drugie wywołanie WriteLine wewnątrz bloku try. Jaki jest problem z kontynuowaniem wykonania w bloku catch?

Z oczywiście część zwrotu plonu (obecnie) nie jest w stanie niczego rzucić, ale dlaczego miałoby to powstrzymać nas od posiadania Enklawy try/catch aby poradzić sobie z wyjątkami rzuconymi przed lub po yield return?

Update: jest ciekawy komentarz Erica Lipperta tutaj - wydaje się, że mają już wystarczająco dużo problemów z prawidłowym wdrożeniem try/wreszcie zachowania!

EDIT: strona MSDN o tym błędzie jest: http://msdn.microsoft.com/en-us/library/cs1x15az.aspx to nie wyjaśnia dlaczego.

Author: Mike Christiansen, 2008-12-06

4 answers

Podejrzewam, że jest to kwestia praktyczności, a nie wykonalności. Podejrzewam, że jest bardzo, bardzo kilka razy, kiedy to ograniczenie jest w rzeczywistości problemem, którego nie można obejść - ale dodatkowa złożoność w kompilatorze byłaby bardzo znacząca.

Jest kilka takich rzeczy, które już spotkałem:

  • atrybuty nie mogące być ogólne
  • niemożność wyprowadzenia X z X. Y (Klasa zagnieżdżona w X)
  • bloki iteratora używanie publicznych pól w generowanych klasach

W kaĹźdym z tych przypadkăłw moĹźna uzyskaÄ ‡ nieco wiÄ ™ kszÄ ... wolnoĹ "ć, kosztem dodatkowej zĹ 'oĺźonoĺ" ci kompilatora. Zespół dokonał pragmatycznego wyboru , za co pochwalam ich-wolałbym mieć nieco bardziej restrykcyjny język z 99,9% dokładnym kompilatorem (tak, są błędy; wpadłem na jeden, więc tylko drugi dzień) niż bardziej elastyczny język, który nie mógł poprawnie skompilować.

EDIT: Oto pseudo-dowód na to, dlaczego jest wykonalny.

Weź pod uwagę, że:

  • możesz się upewnić, że sama część zwrotu wydajności nie wyrzuci wyjątku (wstępnie Oblicz wartość, a następnie ustaw pole i zwróć "true")
  • dozwolone jest try/catch, który nie używa zwrotu wydajności w bloku iteratora.
  • wszystkie zmienne lokalne w bloku iteratora są zmiennymi instancji w generowanym typie, dzięki czemu można swobodnie przenosić kod do nowego metody

Teraz transformacja:

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

Into (rodzaj pseudo-kodu):

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

Jedyną duplikacją jest konfigurowanie bloków try/catch - ale to jest coś, co kompilator z pewnością może zrobić.

Mogłem coś tu przeoczyć - jeśli tak, daj mi znać!

 51
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-06 21:27:13

Wszystkie polecenia yield w definicji iteratora są konwertowane do stanu w maszynie stanowej, która efektywnie używa instrukcji switch do zaawansowania Stanów. Jeśli wygeneruje kod dla yield poleceń w try/catch, będzie musiał zduplikować wszystko w bloku try dla każdego yield Instrukcja wyłączając każde inne yield polecenie dla tego bloku. Nie zawsze jest to możliwe, szczególnie jeśli jedno yield jest zależne od wcześniejszego.

 5
Author: Mark Cidade,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-06 16:15:59

Spekulowałbym, że ze względu na sposób, w jaki stos wywołań jest nawijany/rozwijany, gdy zwracasz z enumeratora, niemożliwe staje się, aby blok try/catch faktycznie "złapał" wyjątek. (ponieważ blok powrotu wydajności nie znajduje się na stosie, mimo że był on inicjatorem bloku iteracji)

Aby dowiedzieć się, o czym mówię, skonfiguruj blok iteratora i foreach używając tego iteratora. Sprawdź jak wygląda stos wywołań wewnątrz bloku foreach, a następnie sprawdź wewnątrz iteratora try / finally block.

 2
Author: Radu094,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-06 15:45:52

Zaakceptowałem odpowiedź niezwyciężonego SKEETA, dopóki nie pojawi się ktoś z Microsoftu, by wylać zimną wodę na pomysł. Ale nie zgadzam się z kwestią opinii-oczywiście poprawny kompilator jest ważniejszy niż kompletny, ale kompilator C# jest już bardzo sprytny w uporządkowaniu tej transformacji dla nas tak dalece, jak to robi. Nieco więcej kompletności w tym przypadku sprawiłoby, że język byłby łatwiejszy w użyciu, nauczaniu, wyjaśnianiu, z mniejszą liczbą przypadków krawędziowych lub gotchas. Więc myślę, że tak. warto się postarać. Kilku facetów w Redmond drapie się po głowach przez dwa tygodnie, a w rezultacie miliony programistów w ciągu następnej dekady mogą się trochę zrelaksować.

(mam również nikczemne pragnienie, aby był sposób, aby yield return rzucić wyjątek, który został wepchnięty do maszyny państwowej "z zewnątrz", przez kod napędzający iterację. Ale moje powody, by tego chcieć, są dość niejasne.)

Właściwie jedno pytanie, które mam na temat odpowiedzi Jona, dotyczy wydajności return expression throwing.

Oczywiście yield return 10 nie jest taki zły. Ale to by było złe:

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

Więc nie byłoby sensu oceniać tego wewnątrz poprzedzającego 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 będą zagnieżdżone bloki try/catch i powtórzone wyjątki:

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...
 2
Author: Daniel Earwicker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-07 00:38:43