Наконец, блок не может быть вызван при перечислении метода выхода

Я обнаружил ситуацию, когда блок finally не вызывается.

К точке:

using System;
using System.Collections.Generic;
using System.Threading;
using System.ComponentModel;

    class MainClass{
        static IEnumerable<int> SomeYieldingMethod(){
            try{
                yield return 1;
                yield return 2;
                yield return 3;
            }finally{
                Console.WriteLine("Finally block!");
            }
        }

        static void Main(){
            Example(7);
            Example(2);
            Example(3);
            Example(4);
        }

        static void Example(int iterations){
            Console.WriteLine("\n Example with {0} iterations.", iterations);
            var e = SomeYieldingMethod().GetEnumerator();
            for (int i = 0; i< iterations; ++i) {
                Console.WriteLine(e.Current);
                e.MoveNext();
            }
        }
    }

И результат:

Example with 7 iterations.
0
1
2
3
Finally block!
3
3
3

Example with 2 iterations.
0
1

Example with 3 iterations.
0
1
2

Example with 4 iterations.
0
1
2
3
Finally block!

Итак, похоже, что если кто-то использует мой метод yield и работает с ним вручную, используя перечислители (не с foreach), то мой блок finally никогда не может быть вызван.

Есть ли способ убедиться, что мой метод завершает свои ресурсы? И это ошибка в «синтаксическом сахаре выхода» или он работает так, как должен быть?


person PanDenat    schedule 12.11.2013    source источник
comment
Это вовсе не дубликат. Это спрашивает, вызывается ли блок finally при удалении перечислителя. Связанный вопрос спрашивает, почему вы не можете использовать yield в блоке try/catch. Обратите внимание, что вы можете использовать yield в блоке try/finally, как это делает этот код.   -  person Joey Adams    schedule 19.11.2014


Ответы (2)


Это потому, что они неправильно использовали итератор. Этот код должен быть:

using(var e = SomeYieldingMethod().GetEnumerator())
{
    for (int i = 0; i< iterations; ++i) {
        Console.WriteLine(e.Current);
        e.MoveNext();
    }
}

using здесь важно. Это то, что соответствует finally и т. д. (при условии, что итератор еще не завершился естественным образом).

В частности, foreach явно вызывает Dispose() для итератора, если итератор реализует IDisposable. Начиная с IEnumerator<T> : IDisposable сюда входят почти все итераторы (предостережение: foreach не требует IEnumerator[<T>]).

person Marc Gravell    schedule 12.11.2013

Он работает по назначению, наконец, выполняется при ошибке или когда вы добираетесь до блока, если они никогда не перечисляются до конца, вы никогда не доберетесь до конца этого блока, поэтому, наконец, не должно выполняться.

Yield создает класс для представления вашего метода как метода + состояние, помещенное в члены + место для возобновления, поэтому вы «возобновляете» метод каждый раз, вы никогда не доберетесь до окончательного, если ваш наконец помещается в конец перечисления И вы никогда не перечисляете до конца. Если вы хотите, чтобы блок finally выполнялся каждый раз, вам нужно будет выполнить нетерпеливую оценку, но вы не можете сказать что-то вроде «я хочу, чтобы это выполнялось в потоковом режиме и, наконец, вызывалось в конце, но чтобы компилятор автоматически обнаруживал в клиентский код, если он никогда не будет выполнять потоковую передачу до конца, а затем все равно вызывать наконец». Нет «правильного» времени для вызова finally, поскольку все, что вы знаете, он может передать перечислитель другому проекту и продолжить перечисление позже, что тогда произойдет, если он продолжит перечисление и компилятор ошибочно вызван finally?

person Ronan Thibaudau    schedule 12.11.2013
comment
Подходящего времени для звонка, наконец, нет, - на самом деле очень много: это время четко определено. Это происходит, когда итератор удаляется (при условии, что он еще не завершен). - person Marc Gravell; 12.11.2013