Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/jpa/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# IEnumerable的异常处理问题<;T>;,它';懒散依赖_C#_Exception Handling_Ienumerable - Fatal编程技术网

C# IEnumerable的异常处理问题<;T>;,它';懒散依赖

C# IEnumerable的异常处理问题<;T>;,它';懒散依赖,c#,exception-handling,ienumerable,C#,Exception Handling,Ienumerable,每当我想指定某个特定的输出为只读时,我就用IEnumerable作为返回类型创建接口。我喜欢它,因为它非常简约,隐藏了实现细节,并将被调用方与调用方分离 但最近,我的一位同事认为,对于只涉及惰性计算的场景,应该保留IEnumerable,否则调用方方法就不清楚了,异常处理应该在哪里发生——在方法调用或迭代周围。然后对于具有只读输出的即时评估案例,我应该使用ReadOnlyCollection 听起来对我来说很合理,但你有什么建议吗?你同意那个关于IEnumerable的约定吗?或者在IEnume

每当我想指定某个特定的输出为只读时,我就用
IEnumerable
作为返回类型创建接口。我喜欢它,因为它非常简约,隐藏了实现细节,并将被调用方与调用方分离

但最近,我的一位同事认为,对于只涉及惰性计算的场景,应该保留
IEnumerable
,否则调用方方法就不清楚了,异常处理应该在哪里发生——在方法调用或迭代周围。然后对于具有只读输出的即时评估案例,我应该使用
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");
        }
    }
}
可枚举的公共类
{
IEnumerable Throw()
{
抛出新ArgumentException();
}
IEnumerable lazytrow()
{
foreach(抛出()中的变量项)
{
收益回报项目;
}
}
公开募捐
{
尝试
{
投掷();
}
捕获(异常)
{
控制台写入线(“立即抛出”);
}
尝试
{
懒散的;
}
捕获(异常)
{
WriteLine(“没有抛出异常”);
}
尝试
{
foreach(lazytrow()中的变量项)
{
//做smth
}
}
捕获(异常)
{
Console.WriteLine(“懒散抛出”);
}
}
}

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

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

IEnumerable<int> LazyThrow() {
     // TODO: check args, throwing exception
     return LazyThrowImpl();
}
IEnumerable<int> LazyThrowImpl() {
    // TODO: lazy code using yield
}
IEnumerable lazytrow(){
//TODO:检查参数,引发异常
返回LazyThrowImpl();
}
IEnumerable LazyThrowImpl(){
//TODO:使用yield的惰性代码
}

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

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

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


然而,我不会把它当作反对IEnumerable的一般论点。老实说,大多数时候,当我枚举的集合抛出异常时,我不知道如何处理它,基本上它会转到某个全局错误处理程序。当然,我同意抛出异常的那一刻可能令人困惑。

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

但是在实践中,您无法查看返回类型并推断出任何太有用的内容。即使将返回类型设置为ReadonlyCollection,它仍然可能会延迟求值和抛出,例如(ReadonlyCollection不是密封的,因此子类可以显式实现IEnumerable接口并执行奇怪的操作)

归根结底,.Net类型的系统不会帮助您对大多数对象的异常行为进行太多的推理


事实上,我仍然会选择返回ReadonlyCollection而不是IEnumerable,但因为与IEnumerable相比,它公开了更有用的方法(比如O(1)对第n个元素的访问)。如果您要生成一个由数组支持的清单集合,那么您最好将该集合的有用方面反馈给消费者。(您也可以只返回调用者拥有的“fresh”数组。)

假设IEnumerable应该保留为惰性求值是毫无意义的,因为它从一开始就不与惰性求值绑定。IEnumerable存在于C#中常用的惰性求值范式之前。这是一个有用的片段,但我的问题实际上并不限于检查参数,任何异常都可能在惰性求值过程中发生(即在LazyThrowImpl中)。因此,我想知道的是,保持IEnumerable仅适用于惰性评估案例是否是一种合理的约定,从而帮助调用方理解异常处理代码必须在哪里。我的观点是,任何异常都可能在常规评估期间发生,因此这是一个没有实际意义的观点。好的,更普遍的情况发生在懒惰评估中,但我仍然要说,一般来说,不要担心。对不起,我理解你的意思,但我不知道它如何回答这个问题(@yacoder我的观点是,没有IEnumerable与延迟求值无关。所有实现该接口和列表的BCL泛型集合都是mit延迟求值的。此外,声明返回类型并不是说它是只读的。它只是说,唯一可以指望的是获得枚举