Получение ссылки на исключение в блоке finally в итераторе - PullRequest
8 голосов
/ 17 января 2011

Есть ли способ получить ссылку на исключение внутри блока finally в функции или свойстве итератора , которые позволяют try..finally, но не try..catch ?

IЯ не собираюсь использовать это, чтобы изменить или связываться с потоком управления, но хотел бы иметь возможность получить ссылку на исключение в блоке finally в любом случае (если оно было выброшено), чтобы прочитать из него и, возможно, добавитьматериал для члена данных .

Я понимаю, что из-за природы сгенерированного компилятором классов из итераторов, это, вероятно, невозможно / разрешено по той же причине, почему try..catch вокругЗаявление о доходности не допускается в первую очередь.Но я все еще надеюсь, что, возможно, найдется какой-то способ (или даже уродливый трюк), чтобы все равно получить исключение.

Упрощенный пример:

IEnumerable<SomeClass> Something
get
{
  try
  {
    throw new SomeException();
    yield return new SomeClass();
  }
  finally
  {
    Exception ex = ... // <= TODO - get hold of the exception here [if one was thrown]...
  }
}

Ответы [ 4 ]

18 голосов
/ 19 января 2011

Это очень интересный вопрос.

Напомним, что в Linq предусмотрено множество стандартных операторов, которые эффективно сцепляются друг с другом.В настоящее время нет такого, который позволял бы вам оборачивать пользовательскую обработку исключений во внутреннюю последовательность.

Поэтому я предлагаю написать новую, которая позволит вам указать действие, которое обрабатывает любое исключение, которое происходит во время выполненияIEnumerator.MoveNext:

public static class EnumerableExceptions
{
    public static IEnumerable<TItem> Catch<TItem, TEx>(
        this IEnumerable<TItem> source, 
        Action<TEx> handler) where TEx : Exception
    {
        using (var enumerator = source.GetEnumerator())
        {
            for (; ; )
            {
                try
                {
                    if (!enumerator.MoveNext())
                        yield break;
                }
                catch (TEx x)
                {
                    handler(x);
                    yield break;
                }

                yield return enumerator.Current;
            }
        }
    }
}

Итак, предположим, что у нас было это:

public class NastyException : Exception { }

public static IEnumerable<String> StringYielder()
{
    yield return "apple";
    yield return "banana";

    throw new NastyException();

    yield return "oracle";
    yield return "grapefruit";
    yield return "microsoft";
}

Мы бы хотели иметь возможность обернуть все тело в try / catch, что, к сожалению, незаконно.Но то, что мы можем сделать, это обернуть сгенерированную последовательность:

public static IEnumerable<String> LoggingStringYielder()
{
    return StringYielder().Catch(
        (NastyException ex) => 
            Console.WriteLine("Exception caught: " + ex.StackTrace));
}

То есть, я получаю последовательность, вызывая «сырой» метод StringYielder, а затем применяю к ней новый оператор Catchуказав, что делать, если возникает определенный тип исключения.Здесь я просто собираюсь напечатать трассировку стека.

Итак, если я сделаю это:

foreach (var str in LoggingStringYielder())
    Console.WriteLine(str);

Программа завершится без сбоев, и получится:

apple
banana
Exception caught:    at ConsoleApplication7.Program.<StringYielder>.. blah

Так что, хотя вы не можете поместить ловушку для кода в исходный метод итератора, теперь вы можете "обернуть" его вокруг внешней части этого метода итератора.Это как не навязчивый способ внедрения обработки исключений вокруг кода между каждый yield return.

Бонусное обновление!

Быть на самом делепридирчиво к тому, как я сформулировал последнее предложение:

  • Во-первых, вы можете бросить перед первым yield return, и он обрабатывается так же, как этот код выполняется при первом вызове MoveNext.Таким образом, «... код перед каждый ...» был бы более точным, чем «... код между каждый ...».

  • Во-вторых, yield return может принимать выражение, которое должно быть оценено, и которое может быть выброшено во время оценки.Это следует рассматривать как код, который выполняется до появления yield return, хотя синтаксически он появляется после него.

5 голосов
/ 17 января 2011

Как насчет перемещения всего кода, который может сгенерировать исключение, во вложенный try / catch:

IEnumerable<int> GetFoo()
{
    for (int i = -10; i < 10; i++)
    {
        Exception ex = null;
        try
        {
            int result = 0;
            try
            {
                result = 10 / i;
            }
            catch (Exception e) // Don't normally do this!
            {
                ex = e;
                throw;
            }
            yield return result;
        }
        finally
        {
            if (ex != null)
            {
                // Use ex here
            }
        }
    }
}

Однако с помощью приведенного выше шаблона вы сможете сделать все, что вам нужно, только в рамках catchвместо этого блок, который был бы проще - вы можете избавиться от окружающего блока try / finally.

2 голосов
/ 17 января 2011

Знание того, какое исключение ожидает, если оно есть, во время финализатора - хорошая возможность. VB.net делает это возможным, хотя и неловко. C # нет. В vb.net методика такая:

  Dim PendingException as Exception = Nothing
  Try
    .. Do Whatever
    PendingException = Nothing ' Important -- See text
  Catch Ex As Exception When CopyFirstArgumentToSecondAndReturnFalse(Ex, PendingException)
    Throw ' Should never happen if above function returns false like it should
  Finally
    ' PendingException will be Nothing if Try completed normally, or hold exception if not.
    Try
      .. Cleanup
    Catch Ex as Exception
      Throw New FailedCleanupException(Ex, PendingException)
    End Try
  End Try

Обратите внимание, что это может быть очень полезным методом, если гарантируется, что код, который будет вызван исключением, в конечном итоге либо сгенерирует исключение, либо сгенерирует новое статистическое исключение. Среди прочего, если исключение будет в конечном итоге необработанным, ловушка отладчика «Необработанное исключение» будет срабатывать при возникновении исходного исключения, а не при последнем его вызове. Это может значительно облегчить отладку, так как для отладчика будет доступно много состояния программы, которое в противном случае было бы сброшено.

Обратите внимание, что PendingException явно очищается в конце основного блока Try. PendingException может иметь значение, даже если нет ожидающих исключений. Это может произойти, если что-то в блоке Try с двойным вложением выдает исключение, которое не будет перехвачено в нашем блоке try, а предложение finally внутреннего блока Try выдает исключение, которое перехватывается в блоке Try с одиночным вложением. В этом случае исходное исключение фактически исчезает. Возможно, будет полезно сгенерировать специальную запись в журнале, если либо «CopyFirstParameterToSecondAndReturnFalse», либо «PendingException = Nothing» выполняется, когда PendingException не равно NULL, поскольку этот сценарий, вероятно, будет представлять собой ошибку, которую нельзя регистрировать где-либо еще, но если есть несколько вложенных блоков catch, они могут генерировать избыточные записи журнала.

Учитывая, что C # не поддерживает фильтрацию исключений, необходимую для этого подхода, может быть полезно написать оболочку VB, которая может вызывать делегаты C #, но обеспечивает необходимую логику обработки исключений.

EDIT Невозможно выполнить возврат в блоке try-catch, но я не вижу, чтобы это вызывало проблему с чем-то вроде:

{
  Exception ex = null;

  try
  {
    CaptureExceptionInfoButDontCatch(ex,{
       /* Stuff that might throw */
    });
    yield return whatever;
    CaptureExceptionInfoButDontCatch(ex,{
       /* More that might throw */
    });
  }
  finally
  {
    /* If ex is nothing, exception occurred */
  }
}
1 голос
/ 17 января 2011
Блоки

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

Использование блока catch сповторный бросок должен работать на вас:

try
{
    // .. something throws here ..
}
catch (Exception ex)
{
    // .. do whatever you need with ex here ..

    // and pass it on
    throw;
} 
...