我通过反射器在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 }
我认为如果有两个以上的项目满足条件,他们应该打破循环,为什么它们总是遍历整个集合?如果反射器错误地分解了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异常是 头脑异常 ,通常不应将其用于控制流。 此类情况无需通过库进行优化。
使用Single零或多个匹配项的代码是完全 有效的 可能性,例如:
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当我们期望序列 仅 包含一个匹配项时,应保留to情况的使用。它的行为应与序列相同,First但带有附加的 运行时断言 ,即该序列不包含多个匹配项。像任何其他断言一样, 失败(即Single抛出异常的情况)应用于表示程序中的错误(在运行查询的方法中或在调用方传递给它的参数中)。
First
这给我们留下了两种情况:
综上所述,如果“性能较差的实施”在生产中影响了您的性能,则可以:
编辑:澄清了我的观点。
编辑:这是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); }