c# - 包装一个 IEnumerable 并捕获异常

标签 c# exception ienumerable 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: Cannot yield a value in the body of a try block with a catch clause .

所以我想写一些概念上等价但可以编译的东西。 :-) 我有这个:

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

但这比我希望的要复杂,我不能完全确定它的正确性或者没有更简单的方法。

我走在正确的轨道上吗?有没有更简单的方法?

最佳答案

如果您想做的是在处理枚举结果期间处理异常,那么您的尝试逻辑只需要直接进入您的 for/while 循环。

但是您的示例读起来好像您正在 try catch 并跳过枚举提供程序引发的异常。

据我所知,在 C# 中无法迭代枚举器并跳过枚举器本身内发生的异常。 如果枚举器引发异常,则以后对 MoveNext() 的所有调用都将导致错误输出。

解释为什么会发生这种情况的最简单方法是使用这个非常简单的可枚举:

IEnumerable<int> TestCases()
{
    yield return 1;
    yield return 2;
    throw new ApplicationException("fail eunmeration");
    yield return 3;
    yield return 4;
}

可以理解,当我们看这个例子时,很明显抛出的异常将导致整个 block 退出,并且永远不会处理第 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;
        }
    }
}

当引发异常时,此 block 的处理停止并将调用堆栈传递回最近的异常处理程序。

我在 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

我希望这能为 future 的其他开发人员解决这个问题,因为这是一个非常普遍的想法,一旦我们开始深入了解 Linq 和枚举,我们都会明白这一点。功能强大,但存在一些逻辑限制。

关于c# - 包装一个 IEnumerable 并捕获异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3835633/

相关文章:

c++ - 从 C++ 异常的构造函数中抛出异常

java - 我应该如何做到这一点而不引发异常?

c# - 获取 IEnumerable 错误 : CS1061 does not contain C# ASP. NET MVC5

c# - 在 MVC(4) 中动态更改 css 的常见做法

c# - 过滤器函数的奇数签名

c# - 检查一个列表是否包含另一个列表中的任何元素

c# - 我应该在界面中指定 IQueryable 而不是 List 吗?

c# - C 和 C# 实现之间的 AES/CBC 加密差异

java - 当必须执行步骤 2 时,处理步骤 1 中异常的最佳方法是什么

c# - IEnumerable <T>的实现适用于foreach,但不适用于LINQ