使用LINQ检查列表是否为空

时间:2020-03-05 18:46:55  来源:igfitidea点击:

确定列表是否为空的"最佳"方式(兼顾速度和可读性)是什么?即使列表的类型为IEnumerable <T>并且没有Count属性。

现在,我在这之间折腾:

if (myList.Count() == 0) { ... }

还有这个:

if (!myList.Any()) { ... }

我的猜测是第二个选项更快,因为它会在看到第一个项目后立即返回结果,而第二个选项(对于IEnumerable)将需要访问每个项目以返回计数。

话虽这么说,第二种选择对我们来说可读吗?你更喜欢哪个?还是我们可以想到一种更好的方法来测试空列表?

Edit @lassevk的响应似乎是最合乎逻辑的,再加上一些运行时检查,以便在可能的情况下使用缓存的计数,如下所示:

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list is ICollection<T>) return ((ICollection<T>)list).Count == 0;

    return !list.Any();
}

解决方案

回答

我们可以这样做:

public static Boolean IsEmpty<T>(this IEnumerable<T> source)
{
    if (source == null)
        return true; // or throw an exception
    return !source.Any();
}

编辑:请注意,如果基础源实际上具有快速的Count属性,则仅使用.Count方法将很快。上面的有效优化将是检测一些基本类型,并简单地使用这些基本类型的.Count属性,而不是.Any()方法,但是如果无法保证,则退回到.Any()。

回答

我刚刚写了一个快速测试,请尝试以下操作:

IEnumerable<Object> myList = new List<Object>();

 Stopwatch watch = new Stopwatch();

 int x;

 watch.Start();
 for (var i = 0; i <= 1000000; i++)
 {
    if (myList.Count() == 0) x = i; 
 }
 watch.Stop();

 Stopwatch watch2 = new Stopwatch();

 watch2.Start();
 for (var i = 0; i <= 1000000; i++)
 {
     if (!myList.Any()) x = i;
 }
 watch2.Stop();

 Console.WriteLine("myList.Count() = " + watch.ElapsedMilliseconds.ToString());
 Console.WriteLine("myList.Any() = " + watch2.ElapsedMilliseconds.ToString());
 Console.ReadLine();

第二个几乎慢了三倍:)

再次尝试使用Stack或者数组或者其他方案进行秒表测试时,它实际上取决于显示的列表类型,因为它们证明Count较慢。

因此,我想这取决于我们使用的列表类型!

(只需指出,我将2000+个对象放入列表中,并且计数仍然更快,与其他类型相反)

回答

如果我们有多个项目,第二个选项会更快。

  • 只要找到1个项目,Any()就会返回。
  • Count()必须一直遍历整个列表。

例如,假设枚举有1000个项目。

  • Any()将检查第一个,然后返回true。
  • 遍历整个枚举后,Count()将返回1000。

如果我们使用谓词重写之一,则Count()仍然必须检查每个项目,即使只有一个匹配项,也可能会更糟。

我们习惯于使用Any确实有意义并且可读。

一个警告,如果我们有一个列表,而不只是一个IEnumerable,则使用该列表的Count属性。

回答

LINQ itself must be doing some serious optimization around the Count() method somehow.

这会让我们感到惊讶吗?我想对于IList实现,Count只是直接读取元素数,而Any则必须查询IEnumerable.GetEnumerator方法,创建实例并至少调用一次MoveNext。

/编辑@Matt:

I can only assume that the Count() extension method for IEnumerable is doing something like this:

是的,当然可以。这就是我的意思。实际上,它使用的是" ICollection"而不是" IList",但是结果是相同的。

回答

@Konrad让我感到惊讶的是,在我的测试中,我将列表传递给了一个接受IEnumerable <T>的方法,因此运行时无法通过调用IList <T的Count()扩展方法来对其进行优化。 >`。

我只能假设IEnumerable的Count()扩展方法正在执行以下操作:

public static int Count<T>(this IEnumerable<T> list)
{
    if (list is IList<T>) return ((IList<T>)list).Count;

    int i = 0;
    foreach (var t in list) i++;
    return i;
}

……换句话说,是针对" IList <T>"的特殊情况的运行时优化。

/ EDIT @Konrad +1伴侣,很可能是在ICollection &lt;T>上。

回答

此扩展方法适用于我:

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    try
    {
        enumerable.First();
        return false;
    }
    catch (InvalidOperationException)
    {
        return true;
    }
}

回答

好的,那这个呢?

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    return !enumerable.GetEnumerator().MoveNext();
}

编辑:我刚刚意识到有人已经草绘了此解决方案。有人提到Any()方法可以做到这一点,但是为什么不自己做呢?问候

回答

另一个想法:

if(enumerable.FirstOrDefault() != null)

但是我更喜欢Any()方法。

回答

根据微软的文档,List.Count是O(1):
http://msdn.microsoft.com/zh-CN/library/27b47ht3.aspx

所以只用List.Count == 0它比查询快得多

这是因为它有一个名为Count的数据成员,该成员在每次添加或者删除列表时都会进行更新,因此,当我们调用List.Count时,它不必遍历每个元素来获取它,它只返回数据成员。

回答

我将对我们似乎已定下的代码进行一些补充:还检查ICollection,因为即使某些非过时的泛型类(例如," Queue <T>"和Stack &lt; T>)。我也将使用as代替is,因为它更加惯用并且被证明是更快的。

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list == null)
    {
        throw new ArgumentNullException("list");
    }

    var genericCollection = list as ICollection<T>;
    if (genericCollection != null)
    {
        return genericCollection.Count == 0;
    }

    var nonGenericCollection = list as ICollection;
    if (nonGenericCollection != null)
    {
        return nonGenericCollection.Count == 0;
    }

    return !list.Any();
}