c # уступить и попробовать окончательно - PullRequest
5 голосов
/ 25 мая 2011

Если у меня есть сопрограмма следующим образом, будет ли вызван код в блоке finally?

public IEnumerator MyCoroutine(int input)
{
  try
  {
    if(input > 10)
    {
      Console.WriteLine("Can't count that high.");
      yield break;
    }
    Console.WriteLine("Counting:");
    for(int i = 0; i < input; i++)
    {
      Console.WriteLine(i.ToString());
      yield return null;
    }
  }
  finally
  {
    Console.WriteLine("Finally!");
  }
}

Ответы [ 4 ]

18 голосов
/ 25 мая 2011

Пока итератор / перечислитель расположен правильно (вызывается IDisposable.Dispose()), тогда да:

Управление всегда передается блоку finally независимо от выхода из блока try.

http://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx

Однако, будьте осторожны с тем, что Dispose() действительно вызывается, иначе вы можете в лучшем случае получить непредвиденные результаты.Для получения дополнительной информации об этом явлении и некоторых проблемах, на которые следует обратить внимание, ознакомьтесь с этой записью в блоге:

Доходность и использование - ваше распоряжение не может быть вызвано!Спасибо Скотту Б за предоставленную ссылку и размещение в ответе, поскольку, кажется, что все ее упускают)

Дополнительно:

Заявление о возврате дохода не может быть найдено где-либо внутрипоймать блок.Он может быть расположен в блоке try, если за блоком try следует блок finally.

http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx

13 голосов
/ 07 декабря 2011

Во всех ответах до сих пор пропущена важная деталь: код в блоке finally, который заключает в себе yield return, будет выполнен, если и когда IDisposable.Dispose вызывается для итератора / перечислителя, который выполнил yield return. Если внешний код вызывает GetEnumerator() на итераторе, а затем вызывает MoveNext(), пока итератор не выполнит yield return в блоке finally, а внешний код затем покинет перечислитель без вызова Dispose, код в finally блок не будет работать. В зависимости от того, что делал итератор, он может быть уничтожен сборщиком мусора (хотя и не имеет возможности очистить какие-либо внешние ресурсы), или он может в конечном итоге окончательно или полупостоянно укорениться как утечка памяти (что может произойти, если например, он прикрепил лямбда-выражение к обработчику событий долгоживущего объекта).

Обратите внимание, что хотя и vb, и c # очень хороши для обеспечения того, чтобы циклы foreach вызывали Dispose для перечислителей, возможно использовать итераторы, явно вызывая GetEnumerator(), и возможно, что некоторый код может сделать это без вызова Dispose(). Итератор мало что может с этим поделать, но любой, кто пишет итераторы, должен знать о возможности.

1 голос
/ 25 мая 2011

Если вам просто лень добавить Main() и т. Д., Получите код отсюда, запустите его и посмотрите, что произойдет:

YieldReturnAndFinally

Ответ на комментарий @Henk Holterman

Действителен любой из следующих четырех:

* IEnumerable
* IEnumerable<T>
* IEnumerator
* IEnumerator<T>
1 голос
/ 25 мая 2011

Согласно документации , да, код в finally всегда будет вызываться.

Поскольку вы используете yield, блок finally не будет выполняться, пока вы не получите доступ к возвращенному IEnumeratorпо методу.Например:

void Main()
{
    var x = MyCoroutine(12);

    //Console.WriteLines will happen when the following
    //statement is executed
    var y = x.MoveNext();
}
...