检查序列是否为空的推荐方法

48

一个方法返回一个序列,IEnumerable<T>,现在你想检查它是否为空。你建议怎么做?我希望既易读性好又性能好。

第一种最明显的方式是检查计数是否大于零:

if(sequence.Count() == 0)

虽然易读性较好,但由于必须遍历整个序列,因此性能很差。

我有时使用以下方法:

if(!sequence.Any())

据我所知,这个过程不必完全进行,但可读性有点反向和笨拙(如果我们检查的序列是非空的,则读起来要好得多)。

另一个选择是在try-catch中使用First,像这样:

try
{
    sequence.First();
}
catch(InvalidOperationException)
{
    // Do something
}

这并不是一种非常漂亮的解决方案,而且可能会更慢,因为它使用了异常等内容。当然,可以通过使用FirstOrDefault来防止这种情况,但如果序列中的第一个项目实际上是默认值,那么你将面临一个大问题;)

那么,有没有其他方法来检查序列是否为空?你通常使用哪种方法?你推荐使用哪种方法?

注意:为了最佳可读性,我可能会将上述其中一个代码片段放在IsEmpty扩展方法中,但我仍然很好奇,因为我还需要在该方法内部执行某些操作: p


sequence 是什么类型? - Neil Barnwell
@Beska:如果该序列没有实现ICollection<T>,它只是通过迭代来实现。请参阅文档:http://msdn.microsoft.com/zh-cn/library/bb338038.aspx - Jon Skeet
@Neil:IEnumerable<T>。我应该在某个地方注明的。正在编辑 :) - Svish
6个回答

82

个人建议使用!sequence.Any()

如果你确实需要的话,你也可以编写自己的扩展方法:

public static bool IsEmpty<T>(this IEnumerable<T> source)
{
    return !source.Any();
}

那么你可以这样写:

if (sequence.IsEmpty())

2
+1,我在自己的应用程序中创建了一个扩展方法,因为“if empty”比“if not any”更容易阅读。 - Meta-Knight
6
+1,查看反射器会发现“sequence.Any()”明显是最佳选择。您是否有任何不想添加此扩展的原因?..我只是想知道在“really”上的斜体强调意味着什么。 - Dynami Le Savard
2
这里有一个不错的IEnumerable<T> IsNullOrEmpty()扩展方法:http://haacked.com/archive/2010/06/10/checking-for-empty-enumerations.aspx - Tim Lewis
3
如果IEnumerable<T>的内容尚未计算(例如您正在使用Linq),问题在于您可能没有意识到会执行两次操作:调用IsEmpty将执行您的Linq查询,然后如果它不为空,您可能会再次执行查询并进行foreach操作。 - user276648
@TimLewis可能是因为这样做实际上并没有增加任何价值,只会使API变得更大,从而更难理解。你会看到SO问题询问Any和IsEmpty之间的性能差异以及哪个更易读等讨论。如果你个人觉得Any(或!Any,字面意思为“不是任何”对我来说很容易理解)太难理解了,你可以自己编写包装方法。 - sara
显示剩余3条评论

6
你可以创建一个扩展方法,并使用以下实现。
public static bool IsEmpty<T>(this IEnumerable<T> items) {
        using (var enumerator = items.GetEnumerator())
        {
            return !enumerator.MoveNext();
        }
 }

嗯,我想这也许就是 Any 方法在底层实际上所做的事情? - Svish
1
你应该释放枚举器。 - mt_serg
供未来读者参考:mt_serg的评论是基于这个答案的原始版本;Manish通过更改为“using”进行了修复,它会自动处理dispose。 - ToolmakerSteve

2

所有这些方法都是LINQ扩展方法,因此取决于如何实现LINQ提供程序。如果您想知道序列是否为空,则Count() == 0Any() == false都可以。我个人更喜欢使用Any()

但是,根据您的sequence的实际类型,您可能不需要使用LINQ扩展方法。例如,如果它是一个数组,您可以调用sequence.Length。如果它是一个集合,您可以使用sequence.Count


我认为建议使用 Count() == 0Any() == false 并列是不太好的,因为已知对于某些 IEnumerables 来说,Count 的计算成本要高得多。因此,选择 Any 更加安全和优越。 - ToolmakerSteve
我更喜欢使用 Any(),虽然在实践中我相信某些类型仍然需要在内部执行计数才能返回结果。此外,Any() 更好地描述了代码的意图,这就是为什么我推荐它而不是 Count() 的原因。 - Neil Barnwell

1
我使用这个扩展方法来检测序列是否为空或没有任何项,或者检测序列是否至少有一项,类似于string.IsNullOrEmpty()方法。
 public static bool IsNullOrEmpty<TSource>(this IEnumerable<TSource> source) {
     if (source == null) {
        return true;
     }

     return !source.Any();
 }

 public static bool IsNotNullOrEmpty<TSource>(this IEnumerable<TSource> source) {
     return !source.IsNullOrEmpty();
 }
 .
 .
 .
 if (!sequence.IsNullOrEmpty()) {
     //Do Something with the sequence...
 }

就第二种方法而言,我建议使用“HasItems”这个名称——比双重否定的“IsNotNullOrEmpty”更短且更易读。 - ToolmakerSteve
糟糕。当我看到你在一个可能为空的值上调用方法时,我误操作按了“踩”,但这是我的错误,因为这是一个扩展方法,所以仍然像静态方法一样被调用,尽管使用了“.”语法。很抱歉,除非答案被编辑,否则我无法取消我的“踩”。我不明白为什么SO不允许在这种情况下取消“踩”。:( - ToolmakerSteve

0

A method that I sometimes use is the following:

if(!sequence.Any())

This doesn't (as far as I know) have to go through the whole sequence, but the readability is a bit backwards and awkward. (Reads a lot better if we are checking that the sequence is not empty though).

根据微软的说法,Any确实不必经过整个序列。引用自“备注”部分:

只要可以确定结果,就会停止对source的枚举。

  • 这在测试是否存在于 if-else 语句中的元素时尤其正确。可以说,如果在 if 语句中测试元素的存在,并在 else 中测试元素的不存在,从而避免使用 ! 运算符,则可读性最佳:

    if (sequence.Any())
    {
    }
    else
    {
    }
    

    与以下代码相比,大多数人认为前者更易读:

    if (!sequence.Any())
    {
    }
    else
    {
    }
    

  • 0

    你说:

    if(sequence.Count() == 0) 可读性不错,但性能很差,因为它必须实际遍历整个序列。

    这是真的吗?你正在谈论一个 接口IEnumerable<T>,然而你正在做关于它的实现可能或可能不正确的假设。事实上,我多年来编写的许多自定义集合都保留了一个私有变量,用于在内部存储当前计数,这意味着返回 .Count 是一件微不足道的事情,不需要遍历整个集合。

    因此,除非你知道特定实现对 .Count 进行了优化,否则我会使用 .Count。尽可能避免过早优化,并坚持可读性。


    5
    就像你所说的那样——你面对的是一个没有明确说明如何计算数量的接口,因此你必须假设它可能是O(n)操作。使用Any而不是Count如何算是过早优化?使用一种专门为此设计并且保证O(1)时间的方法,并不是过早优化,而只是不马虎罢了。 - Greg Beech
    嗯,没错。由于我通常不知道它是什么类型的集合,所以我更喜欢保险起见。只要保险起见不是极其复杂的。而在这种情况下,它并不复杂。但是如果您知道它是列表或数组之类的东西,我同意使用CountLength可能是一个不错的选择。尽管出于一致性考虑,我可能仍然会选择使用IsEmpty扩展方法 :) - Svish
    2
    Count将检查它正在处理的序列是否真的是一个数组,如果是,则返回Length。同样,它将检查ICollection,并返回Count。但是,该序列也可以是由迭代器表达式生成的无限序列,在这种情况下,您将等待很长时间才能找出序列是否为空。当您不知道底层序列类型时,Any()更加可靠。 - Joel Mueller
    1
    如果你正在使用.NET 2.0呢?我有点喜欢提醒人们早期框架版本的这些小“老式”工件,因为有时候人们会忘记并非每个人都能够使用最新最好的版本。 - Nick
    2
    为了日后的读者着想,我要指出扩展方法IEnumerable<T>.Count将检查源是否也是ICollection<T>或ICollection。如果是,则会在此接口上调用Count。否则,扩展方法将枚举整个集合以计算项数。 - Richard Robertson

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