IEnumerable <T>的异常处理问题,它依赖于懒惰

Max*_*kin 7 c# ienumerable exception-handling

IEnumerable<T>每当我想指定特定输出是只读时,我曾经用返回类型创建接口.我喜欢它,因为它是简约的,隐藏实现细节并将被调用者与调用者分离.

但是最近我的一位同事认为IEnumerable<T>应该保留仅涉及延迟评估的场景,否则它不清楚调用方法,异常处理应该采取它的位置 - 围绕方法调用或围绕迭代.那么对于具有只读输出的急切评估案例,我应该使用a ReadOnlyCollection.

对我来说听起来很合理,但你会推荐什么?你同意IEnumerable的约定吗?或者IEnumerable有更好的异常处理方法吗?

如果我的问题不清楚,我做了一个样本课来说明问题.这里的两个方法具有完全相同的签名,但它们需要不同的异常处理:

public class EvilEnumerable
{
    IEnumerable<int> Throw()
    {
        throw new ArgumentException();
    }

    IEnumerable<int> LazyThrow()
    {
        foreach (var item in Throw())
        {
            yield return item;
        }
    }

    public void Run()
    {
        try
        {
            Throw();
        }
        catch (ArgumentException)
        {
            Console.WriteLine("immediate throw");
        }

        try
        {
            LazyThrow();
        }
        catch (ArgumentException)
        {
            Console.WriteLine("No exception is thrown.");
        }

        try
        {
            foreach (var item in LazyThrow())
            {
                //do smth
            }
        }
        catch (ArgumentException)
        {
            Console.WriteLine("lazy throw");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

更新1.问题不仅限于ArgumentException.它是关于创建友好类接口的最佳实践,它告诉您它们是否返回惰性求值结果,因为这会影响异常处理方法.

Mar*_*ell 10

这里真正的问题是延迟执行.在参数检查的情况下,您可以通过添加第二个方法来完成此操作:

IEnumerable<int> LazyThrow() {
     // TODO: check args, throwing exception
     return LazyThrowImpl();
}
IEnumerable<int> LazyThrowImpl() {
    // TODO: lazy code using yield
}
Run Code Online (Sandbox Code Playgroud)

例外发生; 即使对于非延迟结果(List<T>例如),您也可能会遇到错误(可能是在迭代时另一个线程调整列表).上述方法允许您提前尽可能地减少意外的副作用yield和延迟执行.