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

时间:2009-11-15 21:01:34

标签: c# exception-handling ienumerable

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

但是最近我的一位同事认为IEnumerable<T>应该保留用于仅涉及延迟评估的场景,否则对于调用方法不清楚,异常处理应该采用它的方式 - 围绕方法调用或围绕迭代。然后,对于具有只读输出的急切评估案例,我应该使用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");
        }
    }
}

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

4 个答案:

答案 0 :(得分:10)

这里真正的问题是延迟执行。在参数检查的情况下,您可以通过添加第二种方法来实现:

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

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

答案 1 :(得分:3)

理论上,我同意你的同事。如果有一些“工作”来创建结果,并且该工作可能会导致异常,并且您不需要延迟评估,那么确实使结果变得懒惰只会使问题复杂化。

然而在实践中,你无法查看返回类型并推断任何有用的东西。即使您创建了返回类型ReadonlyCollection,它仍然可能会延迟评估和抛出,例如(ReadonlyCollection没有密封,因此子类可以显式实现IEnumerable接口并做一些古怪的事情。)

在一天结束时,.Net类型系统无法帮助您对大多数对象的异常行为做出太多推理。

实际上,我会选择返回一个ReadonlyCollection而不是IEnumerable,但是因为它比IEnumerable暴露了更多有用的方法(比如O(1)访问第n个元素)。如果您要生成由数组支持的清单集合,那么您也可以向消费者提供有用的方面。 (您可能还会返回调用者拥有的“新鲜”数组。)

答案 2 :(得分:2)

我不同意你的同事。该方法的文档应遵循标准,该文档可能会引发异常。将返回类型声明为IEnumerable不会使其只读。它是否只读取决于返回的接口的实际实现。调用者不应该依赖它是可写的,但这与只读的集合非常不同

答案 3 :(得分:0)

在Eric Lippert的帖子中讨论了类似的问题:

http://blogs.msdn.com/ericlippert/archive/2007/09/05/psychic-debugging-part-one.aspx

http://blogs.msdn.com/ericlippert/archive/2007/09/06/psychic-debugging-part-two.aspx

但是,我不会将它视为一般反对IEnumerable的论据。老实说,大多数情况下,当我枚举的集合抛出异常时,我不知道如何处理它,基本上它会转到一些全局错误处理程序。当然,我同意抛出异常的那一刻可能会令人困惑。