Ken*_*Ken 11 c# ienumerable exception wrapper
我有一堆可以Process()对象的类,并返回自己的对象:
public override IEnumerable<T> Process(IEnumerable<T> incoming) { ... }
我想编写一个可以包装其中一个处理器的处理器类,并记录包装Process()方法可能抛出的任何未捕获的异常.我的第一个想法是这样的:
public override IEnumerable<T> Process(IEnumerable<T> incoming) {
    try {
        foreach (var x in this.processor.Process(incoming)) {
            yield return x;
        }
    } catch (Exception e) {
        WriteToLog(e);
        throw;
    }
}
但这不起作用,因为CS1626:不能在带有catch子句的try块的主体中产生值.
所以我想写一些概念上等同但编译的东西.:-)我有这个:
public override IEnumerable<T> Process(IEnumerable<T> incoming) {
    IEnumerator<T> walker;
    try {
        walker = this.processor.Process(incoming).GetEnumerator();
    } catch (Exception e) {
        WriteToLog(e);
        throw;
    }
    while (true) {
        T value;
        try {
            if (!walker.MoveNext()) {
                break;
            }
            value = walker.Current;
        } catch (Exception e) {
            WriteToLog(e);
            throw;
        }
        yield return value;
    }
}
但这比我希望的更复杂,而且我不完全确定它的正确性还是没有更简单的方法.
我在这里走在正确的轨道上吗?有没有更简单的方法?
可以编写linq扩展名,以跳过导致异常的所有元素,并允许您传递操作来处理引发的异常。
public static IEnumerable<T> CatchExceptions<T> (this IEnumerable<T> src, Action<Exception> action = null) 
{
    using (var enumerator = src.GetEnumerator()) 
    {
        bool next = true;
        while (next) 
        {
            try 
            {
                next = enumerator.MoveNext();
            } 
            catch (Exception ex) 
            {
                if (action != null)
                    action(ex);
                continue;
            }
            if (next) 
                yield return enumerator.Current;
        }
    }
}
例:
ienumerable.Select(e => e.something).CatchExceptions().ToArray()
ienumerable.Select(e => e.something).CatchExceptions((ex) => Logger.Log(ex, "something failed")).ToArray()
注意:此解决方案仅捕获在处理原始产生或计划的值时引发的异常,因此,这是处理按OP要求链接IEnumerable表达式时引发的异常的好方法。如果在IEnumerable提供程序中引发了异常,则该枚举被中止的可能性很高,该方法将优雅地处理该异常,但是将无法恢复枚举。
提供程序中引发的异常通常发生在文件系统或数据库读取器中,因为它们易于超时和其他运行时上下文问题。这个小提琴展示了如何创建这样的场景:https : //dotnetfiddle.net/a43Vtt您将必须直接在源代码中处理这些问题。
如果您想要做的是在处理枚举结果期间处理异常,那么您尝试逻辑只需要直接进入for/while循环.
但是您的示例读取就好像您正在尝试捕获并跳过枚举提供程序引发的异常.
据我所知,C#中没有办法迭代枚举器并跳过枚举器本身内发生的异常.如果枚举器引发异常,则将来对MoveNext()的所有调用都将导致错误输出.
解释原因的最简单方法是使用这个非常简单的枚举:
IEnumerable<int> TestCases()
{
    yield return 1;
    yield return 2;
    throw new ApplicationException("fail eunmeration");
    yield return 3;
    yield return 4;
}
可以理解的是,当我们查看这个示例时,很明显抛出的异常会导致整个块退出,并且不会处理第3和第4个yield语句.实际上,在第3个yield语句中获得通常的"无法访问的代码检测"编译器警告.
因此,当可枚举更复杂时,适用相同的规则:
IEnumerable<int> TestCases2()
{
    foreach (var item in Enumerable.Range(0,10))
    {
        switch(item)
        {
            case 2:
            case 5:
                throw new ApplicationException("This bit failed");
            default:
                yield return item;
                break;
        }
    }
}
引发异常时,此块的处理将停止并将调用堆栈传递回最近的异常处理程序.
我在SO上找到的解决这个问题的所有可行示例都没有进入枚举中的下一个项目,它们都在第一个异常时中断.
因此,要在枚举中跳过例外,您需要提供者来促进它.这只有在您编写提供程序时才有可能,或者您可以联系开发人员,以下是如何实现此目的的过度简化示例:
IEnumerable<int> TestCases3(Action<int, Exception> exceptionHandler)
{
    foreach (var item in Enumerable.Range(0, 10))
    {
        int value = default(int);
        try
        {
            switch (item)
            {
                case 2:
                case 5:
                    throw new ApplicationException("This bit failed");
                default:
                    value = item;
                    break;
            }
        }
        catch(Exception e)
        {
            if (exceptionHandler != null)
            {
                exceptionHandler(item, e);
                continue;
            }
            else
                throw;
        }
        yield return value;
    }
}
...
foreach (var item in TestCases3(
    (int item, Exception ex) 
    => 
    Console.Out.WriteLine("Error on item: {0}, Exception: {1}", item, ex.Message)))
{
    Console.Out.WriteLine(item);
}
这将产生以下输出:
0
1
Error on item: 2, Exception: This bit failed
3
4
Error on item: 5, Exception: This bit failed
6
7
8
9
我希望这能为将来的其他开发人员解决这个问题,因为一旦我们开始深入了解Linq和枚举,这是一个非常普遍的想法.强大的东西,但有一些逻辑限制.
Bea*_*key -2
不需要循环和产量(至少在您的示例中)。简单地删除它们,并且不再对 catch 块进行限制。
public override IEnumerable<T> Process(IEnumerable<T> incoming) {
    try {
        return this.processor.Process(incoming);
    } catch (Exception e) {
        WriteToLog(e);
        throw;
    }
}
我假设this.processor.Process(incoming)返回一个实现的集合IEnumerable<T>,因此您不需要创建新的迭代器。如果this.processor.Process(incoming)是懒惰评估那么
public override IEnumerable<T> Process(IEnumerable<T> incoming) {
    try {
        return this.processor.Process(incoming).ToList();
    } catch (Exception e) {
        WriteToLog(e);
        throw;
    }
}