LINQ Any()和Single()与SingleOrDefault()的区别以及对null进行检查

12
在什么情况下,每种解决方案都优于另一种?
示例1:
if (personList.Any(x => x.Name == "Fox Mulder"))
{
  this.Person = personList.Single(x => x.Name == "Fox Mulder");
}

示例2:

var mulder = personList.SingleOrDefault(x => x.Name == "Fox Mulder");

if (mulder != null)
{
  this.Person = mulder;
}

2
我“想象”(虽然我没有进行基准测试)SingleOrDefaultAny更高效。当涉及到Linq时,通常有多种方法可以完成同一件事情。我会选择对你来说最有意义的选项。 - Alex Booker
1
调用 Any 然后 Single 会访问 personList 两次,而使用 SingleOrDefault 只会访问一次。从性能上来看,可能最好调用后者(尽管我没有对此进行测试!) - Ric
3个回答

12

SingleSingleOrDefault 都会枚举集合中第一个匹配结果之后的元素,以验证准确存在一个符合条件的元素,最终停止于下一个匹配项或集合末尾。第一个例子稍微慢一些,因为 Any 调用将枚举足够多的集合(有可能是全部)来确定是否有任何元素满足条件,最终停止于第一个匹配项或集合末尾。

还有一个关键区别:第一个例子可能会抛出异常, Single 在恰好有一个匹配项时返回匹配元素,并在没有匹配项时抛出异常。而使用 Any 不能验证这一点,它只验证是否至少有一个匹配项。

基于这两个原因(特别是第二个原因),这里首选使用 SingleOrDefault 方法。


所以这里有三种情况:

情况1:没有任何项符合条件

选项1:.Any 枚举整个集合并返回 false;.Single 不执行。

选项2:.SingleOrDefault 枚举整个集合并返回 null。

两者实质上是等价的。

情况2:只有一项符合条件

选项1:Any 枚举集合中足够多的元素以找到单个匹配项(可以是第一个元素,也可能是整个集合)。接下来,Single 枚举整个集合以找到该项并确认没有其他匹配项。

选项2:SingleOrDefault 枚举整个集合,返回唯一的匹配项。

在这种情况下,选项2 更好(仅需一次迭代,而选项1和2需要 1 到 2 次迭代)。

情况3:有多项元素符合条件

选项1: Any 枚举足够找到第一次匹配。 Single 枚举足够找到第二次匹配,抛出异常。

选项2: SingleOrDefault 枚举足够找到第二次匹配,抛出异常。

两者都会抛出异常,但选项2会更快地到达。


2
SingleSingleOrDefault不会枚举整个集合 - 如果找到第二个元素,则停止枚举就足够了。 - Jacob Krall
3
根据MSDN的说明,SingleOrDefault()方法在结果元素超过一个时也会抛出异常:https://msdn.microsoft.com/zh-cn/library/vstudio/bb342451(v=vs.100).aspx - user1172282
@JacobKrall 谢谢,我没有考虑到那一点。我已经更新了第一段来反映这一点。 - yoozer8
@JacobKrall,使用linq to objects时,当前实现会继续执行。我已经向.Net Core提交了一个拉取请求,以采用您所描述的行为方式,但尚未决定是否该问题的向后兼容性太重要。 - Jon Hanna
@JacobKrall,https://github.com/dotnet/corefx/pull/2350。不过等我有时间的时候,我需要再次合并到主分支,因为我在另一个PR中做了一些其他的性能改进,所以它应该相应地进行更新。https://github.com/dotnet/corefx/issues/2349是相关的问题。 - Jon Hanna
显示剩余3条评论

0

is v. as 指南推断:

请参见下面,来自 Casting vs using the 'as' keyword in the CLR

// Bad code - checks type twice for no reason
if (randomObject is TargetType)
{
    TargetType foo = (TargetType) randomObject;
    // Do something with foo
}

通过使用AnySingle,您也会检查两次列表。同样的逻辑似乎也适用:这不仅检查两次,而且可能检查不同的东西,即在多线程应用程序中,列表在检查和分配之间可能是不同的。在极端情况下,使用Any找到的项目在调用Single时可能已经不存在。

基于这种逻辑,除非有证据证明否则,我会在所有情况下更喜欢第二个示例。


0

选项3:

this.Person = personList.FirstOrDefault(x => x.Name == "Fox Mulder");

如果使用Entity Framework并且名称是主键,则:
this.person = db.personList.Find("Fox Mulder");

如果this.Person为空或者找不到人,这两个都可以工作,你期望this.Person被覆盖为null。FirstOrDefault是最快的,因为它会在第一个匹配的记录处停止而不是遍历整个集合,但如果找到多个,则不会抛出异常。在这种情况下,实体框架解决方案甚至更好,因为Find将使用EF缓存,可能根本不需要访问数据源。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接