c# - Enumerable.Single 的错误执行?

标签 c# .net linq algorithm

我通过反射器在 Enumerable.cs 中发现了这个实现。

public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    //check parameters
    TSource local = default(TSource);
    long num = 0L;
    foreach (TSource local2 in source)
    {
        if (predicate(local2))
        {
            local = local2;
            num += 1L;
            //I think they should do something here like:
            //if (num >= 2L) throw Error.MoreThanOneMatch();
            //no necessary to continue
        }
    }
    //return different results by num's value
}

我认为如果有超过 2 个项目满足条件,他们应该打破循环,为什么他们总是循环遍历整个集合?以防反射器错误地反汇编 dll,我写了一个简单的测试:

class DataItem
{
   private int _num;
   public DataItem(int num)
   {
      _num = num;
   }

   public int Num
   {
      get{ Console.WriteLine("getting "+_num); return _num;}
   }
} 
var source = Enumerable.Range(1,10).Select( x => new DataItem(x));
var result = source.Single(x => x.Num < 5);

对于这个测试用例,我认为它会打印“getting 0, getting 1”然后抛出异常。但事实是,它一直在“得到 0...得到 10”并抛出异常。 他们像这样实现这个方法有什么算法上的原因吗?

编辑 你们中的一些人认为这是因为谓词表达式的副作用,经过深思熟虑和一些测试用例后,我得出的结论是方面在这种情况下效果并不重要。如果您不同意此结论,请举例说明。

最佳答案

是的,我确实觉得它有点奇怪,尤其是因为不采用谓词的重载(即只对序列起作用)确实似乎具有快速“优化”。


然而,在 BCL 的辩护中,我会说 Single 抛出的 InvalidOperation 异常是 boneheaded exception通常不应将其用于控制​​流。库没有必要对此类情况进行优化。

使用 Single 的代码,其中零个或多个匹配是完全有效的可能性,例如:

try
{
     var item = source.Single(predicate);
     DoSomething(item);
}

catch(InvalidOperationException)
{
     DoSomethingElseUnexceptional();    
}

应该重构为将异常用于控制流的代码,例如(仅示例;这可以更有效地实现):

var firstTwo = source.Where(predicate).Take(2).ToArray();

if(firstTwo.Length == 1) 
{
    // Note that this won't fail. If it does, this code has a bug.
    DoSomething(firstTwo.Single()); 
}
else
{
    DoSomethingElseUnexceptional();
}

换句话说,我们应该将 Single 的使用留给我们期望序列包含一个匹配项的情况。它的行为应该与 First 相同,但带有额外的运行时断言,即序列不包含多个匹配项。与任何其他断言一样,失败,即 Single 抛出的情况,应该用于表示程序中的错误(在运行查询的方法中或传递给的参数中)它由调用者)。

这给我们留下了两种情况:

  1. 断言成立:只有一个匹配项。在这种情况下,我们希望 Single 无论如何 消耗整个序列来声明我们的声明。 “优化”没有任何好处。事实上,有人可能会争辩说,OP 提供的“优化”示例实现实际上会更慢,因为要检查循环的每次迭代。
  2. 断言失败:有零个或多个匹配项。在这种情况下,我们抛出的时间比我们可能的晚,但这没什么大不了的,因为异常是愚蠢的:它表明存在错误必须修复。

总而言之,如果“糟糕的实现”影响了您在生产环境中的性能表现,则:

  1. 您错误地使用了Single
  2. 您的程序中有错误。错误修复后,这个特定的性能问题就会消失。

编辑:澄清了我的观点。

编辑:这是 Single 的有效用法,其中失败表示调用代码中的错误(参数错误):

public static User GetUserById(this IEnumerable<User> users, string id)
{
     if(users == null)
        throw new ArgumentNullException("users");

     // Perfectly fine if documented that a failure in the query
     // is treated as an exceptional circumstance. Caller's job 
     // to guarantee pre-condition.        
     return users.Single(user => user.Id == id);    
}

关于c# - Enumerable.Single 的错误执行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23915815/

相关文章:

c# - 如何只获取可以复制的文件?

c# - 如何根据约定创建返回受限属性集的表达式树?

c# - Http.HttpRequestException Xamarin.forms

c# - 如何告诉 ASP.NET MVC 在文件夹中的每个 View 中包含 cshtml

c# - 启动时对象初始化的模式

.net - 依赖注入(inject)的惰性解析

linq - 定义导航属性时列表 vs IEnumerable vs IQueryable

c# - 使用 LINQ 转换投影列表返回空值列表?

c# - 使用 LINQ 展平嵌套字典

c# - 为什么 enum.ToString() 给出的结果与调试器工具提示中显示的结果不同?