简体   繁体   English

在迭代器中的finally块中引用异常

[英]Getting reference to an exception in finally block in an iterator

Is there any way to get a reference to an exception inside a finally block in an iterator function or property that allow try..finally but not try..catch ? 有没有办法在迭代器函数或属性中的finally块内引用异常,允许try..finally而不是try..catch

I'm not going to use it to change or mess with the control flow, but would like to be able to get a reference to the exception in the finally block anyway (if one was thrown), in order to read from it and possibly add stuff to the Data member . 我不会用它来改变或搞乱控制流,但是希望能够在finally块中获得对异常的引用(如果有人被抛出),以便从中读取并且可能将内容添加到Data成员

I understand that due to nature of the compiler generated classes from iterators, it is probably not possible/allowed for the same reason why try..catch around a yield statement is not allowed in the first place. 我理解由于编译器从迭代器生成类的性质,可能不可能/允许出于同样的原因,首先不允许在yield语句周围使用try..catch。 But I'm still hoping that there is maybe be some way (or even ugly trick) to get hold of the exception anyway. 但是我仍然希望有一些方法(甚至是丑陋的技巧)来控制异常。

Simplified example: 简化示例:

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]...
  }
}

This is a very interesting question. 这是一个非常有趣的问题。

Recall that in Linq, a lot of standard operators are provided that effectively chain together. 回想一下,在Linq中,提供了许多有效链接在一起的标准运算符。 There currently isn't one that lets you wrap custom exception handling around an inner sequence. 目前还没有一个允许您围绕内部序列包装自定义异常处理。

So my suggestion is to write a new one that allows you to specify an action that handles any exception that occurs during the execution of IEnumerator.MoveNext : 所以我的建议是编写一个新的,允许你指定一个动作来处理在执行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;
            }
        }
    }
}

So now supposing we had this: 所以现在假设我们有这个:

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

We'd like to be able to wrap all the body in a try / catch , which is sadly illegal. 我们希望能够将所有的身体包裹在一个try / catch ,这很遗憾。 But what we can do is wrap the generated sequence: 但我们能做的就是包装生成的序列:

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

That is, I get a sequence by calling the "raw" StringYielder method, and then I apply the new Catch operator to it, specifying what to do if a certain exception type occurs. 也就是说,我通过调用“原始” StringYielder方法得到一个序列,然后我将新的Catch运算符应用于它,指定如果发生某种异常类型该怎么做。 Here I'm just going to print the stack trace. 在这里,我将打印堆栈跟踪。

So if I do this: 所以,如果我这样做:

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

The program completes without crashing, and the output is: 程序完成而不会崩溃,输出为:

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

So although you can't put a try catch around the code inside the original iterator method, you can now "wrap" it around the outside of that iterator method. 因此,虽然你不能在原始迭代器方法中尝试捕获代码,但现在可以将它“包装”在迭代器方法的外部。 It's like a non-intrusive way of injecting exception handling around the code between each yield return . 这就像是每个yield return 之间围绕代码注入异常处理的非侵入式方法。

Bonus update! 奖金更新!

To be really picky about the way I worded that last sentence: 对我最后一句话的措辞非常挑剔:

  • Firstly you can throw before the first yield return and it is treated the same way, as that code executes in the first call to MoveNext . 首先,您可以在第一次yield return之前抛出,并且处理方式相同,因为该代码在第一次调用MoveNext So "... the code before each..." would have been more accurate than "... the code between each...". 所以,“......每一个之前的代码......”本来不是更准确“......每一个之间的代码......”。

  • Secondly, a yield return may accept an expression that has to be evaluated, and which may throw during evaluation. 其次, yield return可以接受必须被评估的表达式,并且可以在评估期间抛出。 That should be regarded as code that executes before the yield return occurs, even though syntactically it appears after it. 这应该被视为在yield return发生之前执行的代码,即使它在语法之后出现。

How about moving all the code which could generate an exception into a nested try/catch: 如何将可能生成异常的所有代码移动到嵌套的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
            }
        }
    }
}

With the above pattern, however, you may be able to do everything you need just within the catch block instead, which would be simpler - you may be able to get rid of the surrounding try/finally block. 但是,使用上面的模式,您可以在catch块中执行所需的所有操作,这样可以更简单 - 您可以摆脱周围的try / finally块。

Knowing what exception is pending, if any, during a finalizer is a nice ability. 在终结器期间了解待处理的异常(如果有)是一种很好的能力。 VB.net makes it possible, though awkward. VB.net使它成为可能,尽管很尴尬。 C# does not. C#没有。 In vb.net the technique is: 在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

Note that this can be a very useful technique if code which would be triggered by an exception is guaranteed to end up either rethrowing the exception or throwing a new aggregate exception. 请注意,如果保证由异常触发的代码最终重新抛出异常或抛出新的聚合异常,这可能是一种非常有用的技术。 Among other things, if the exception is going to be ultimately unhandled, the "Unhandled exception" debugger trap will fire when the original exception occurs, rather than the last time it's rethrown. 除其他事项外,如果异常最终未被处理,则在原始异常发生时将触发“未处理异常”调试器陷阱,而不是上次重新抛出异常。 This can considerably ease debugging, since a lot of program state will be available to the debugger that would otherwise be trashed. 这可以大大简化调试,因为调试器可以使用许多程序状态,否则会被破坏。

Note also that the PendingException is explicitly cleared at the end of the main Try block. 另请注意,PendingException在主Try块的末尾显式清除。 It is possible for PendingException to have a value even if there is no pending exception. 即使没有挂起的异常,PendingException也可能具有值。 This could occur if something within a doubly-nested Try block throws an exception which won't be caught within our try block, but the inner Try block's Finally clause throws an exception which is caught within the singly-nested Try block. 如果双重嵌套的Try块中的某些内容抛出一个异常而不会在我们的try块中捕获,则可能会发生这种情况,但内部Try块的Finally子句抛出异常,该异常在单独嵌套的Try块中捕获。 In that case, the original exception effectively disappears. 在这种情况下,原始异常实际上消失了。 It may be good to generate a special log entry if either the the "CopyFirstParameterToSecondAndReturnFalse" or "PendingException = Nothing" executes when PendingException is non-null, since that scenario would probably represent a bug that isn't apt to be logged anywhere else, but if there are multiple nested catch blocks they could generate redundant log entries. 如果PendingException为非null,则执行“CopyFirstParameterToSecondAndReturnFalse”或“PendingException = Nothing”时生成特殊日志条目可能会很好,因为该场景可能代表一个不容易在其他任何地方记录的错误,但是如果有多个嵌套的catch块,它们可以生成冗余的日志条目。

Given that C# doesn't support the exception filtering necessary for this approach, it may be helpful to write a VB wrapper which can call C# delegates but provide the necessary exception-handling logic. 鉴于C#不支持此方法所需的异常过滤,编写一个可以调用C#委托但提供必要的异常处理逻辑的VB包装器可能会有所帮助。

EDIT It's not possible to do a yield return within a try-catch block, but I wouldn't see that would cause a problem with something like: 编辑在try-catch块中不能进行yield return,但是我不会发现这会导致类似下面的问题:

{
  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 */
  }
}

finally blocks are meant for always-executed clean-up and hence are not intended to manipulate with exceptions — there can easily be no pending exception at all. finally块意味着总是执行清理,因此不打算用异常操作 - 很容易就没有挂起的异常。

Using the catch block with a rethrow should work for you: 使用带有重新抛出的catch块应该适合您:

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

    // and pass it on
    throw;
} 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM