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

Kri*_*erA 8 c# ienumerable

有没有办法在迭代器函数或属性中的finally块内引用异常,允许try..finally而不是try..catch

我不会用它来改变或搞乱控制流,但是希望能够在finally块中获得对异常的引用(如果有人被抛出),以便从中读取并且可能将内容添加到Data成员.

我理解由于编译器从迭代器生成类的性质,可能不可能/允许出于同样的原因,首先不允许在yield语句周围使用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]...
  }
}
Run Code Online (Sandbox Code Playgroud)

Dan*_*ker 18

这是一个非常有趣的问题.

回想一下,在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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以现在假设我们有这个:

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";
}
Run Code Online (Sandbox Code Playgroud)

我们希望能够将所有身体都包裹在try/中catch,这很遗憾.但我们能做的就是包装生成的序列:

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

也就是说,我通过调用"raw" StringYielder方法得到一个序列,然后我将new Catch运算符应用于它,指定如果发生某种异常类型该怎么做.在这里,我将打印堆栈跟踪.

所以,如果我这样做:

foreach (var str in LoggingStringYielder())
    Console.WriteLine(str);
Run Code Online (Sandbox Code Playgroud)

程序完成而不会崩溃,输出为:

apple
banana
Exception caught:    at ConsoleApplication7.Program.<StringYielder>.. blah
Run Code Online (Sandbox Code Playgroud)

因此,虽然你不能在原始迭代器方法中尝试捕获代码,但现在可以将它"包装"在迭代器方法的外部.这就像是每个代码之间注入代码的非侵入式方式yield return.

奖金更新!

对我最后一句话的措辞非常挑剔:

  • 首先,您可以在第一个之前抛出yield return它,并且它的处理方式相同,因为该代码在第一次调用时执行MoveNext.所以"...... 每个...... 之前的代码......"比"...... 每个...... 之间的代码"更准确.

  • 其次,a yield return可以接受必须被评估的表达式,并且可以在评估期间抛出.这应该被视为在yield return发生之前执行的代码,即使它在语法之后出现.


Jon*_*eet 5

如何将可能生成异常的所有代码移动到嵌套的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
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,使用上面的模式,您可以在catch块中执行所需的所有操作,这样可以更简单 - 您可以摆脱周围的try/finally块.