使用LINQ检查列表是否为空


122

确定列表是否为空的“最佳”方式(兼顾速度和可读性)是什么?即使列表是类型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();
}

5
更好的是不要混合使用iscast而是使用asnull检查:ICollection<T> collection = list as ICollection<T>; if (collection != null) return colllection.Count;
abatishchev,2010年

2
为什么要写一个额外的方法?不list.Any()等于list.IsEmpty吗?应该优化框架方法-仅当您发现这是一个性能瓶颈时才值得编写一个新方法。
dbkk 2011年

6
是否有人在衡量建议实施的性能,还是每个人都只是在抛出想法?
迈克尔·布朗

我向发布IsEmpty扩展方法的.NET Core类库建议发行。 github.com/dotnet/corefx/issues/35054 如果您愿意并同意,请检查并投票。
RyotaMurohoshi

Answers:


100

您可以这样做:

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()。


4
还是只用一行然后返回(source == null)?true:!source.Any(); (如果您没有抛出异常)
Gage

1
我会说,是的,抛出一个null异常,然后添加第二个扩展方法,称为IsNullOrEmpty()
devuxer 2011年

1
公共静态布尔值IsNullOrEmpty <T>(此IEnumerable <T>源){返回源== null || !source.Any(); }
丹2012年

1
@Gage如今:return !source?.Any() ?? true;
ricksmt

@ricksmt感谢您的更新!我一定会用的!
Gage

14

我将对您似乎已定下的代码进行一些补充:还检查ICollection,因为即使某些非过时的泛型类(也就是Queue<T>and Stack<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();
}

1
我喜欢这个答案。一个警告是,某些集合在未完全实现诸如NotSupportedExceptionor 的接口时会抛出异常NotImplementedException。当我发现一个我正在使用的集合时,我首先使用了您的代码示例,这为Count(谁知道...)抛出了异常。
山姆

1
我了解为什么这样的优化对于需要枚举所有元素的Count()这样的方法很有用。但是Any()最多只需要枚举一个元素,因此我在这里看不到要点。另一方面,要添加的强制转换和if语句是固定的成本,您必须在每次通话时支付。
codymanix

8

LINQ本身必须以某种方式对Count()方法进行一些认真的优化。

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

/编辑@Matt:

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

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


6

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

 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多个对象放入列表中,并且计数仍然更快,与其他类型相反)


12
Enumerable.Count<T>()对进行特殊处理ICollection<T>。如果您尝试使用除基本列表以外的其他方法,则我希望您会看到明显不同(较慢)的结果。Any()但是将保持不变。
马克·格雷韦尔

2
我必须同意马克的观点。这不是一个真正公平的考验。
丹涛

任何想法,为什么没有特殊处理Enumerable.Any<T>()ICollection<T>?当然,无参数Any()也可以只检查Count属性ICollection<T>吗?
Lukazoid 2012年

5

List.Count根据Microsoft的文档为O(1):http :
//msdn.microsoft.com/zh-cn/library/27b47ht3.aspx

因此使用List.Count == 0它比查询要快得多

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


1
如果它是“ IEnumerable”,则否。(对于初学者,IEnumerable没有“ Count”属性,它具有Count()方法。)调用“ Count()”将要求IEnumerable检查列表中的每个单个元素。而“ Any”将在找到1个元素后立即返回。
00jt

这取决于数据源。如果使用yield生成IEnumerable,则必须遍历IEnumerable才能知道其大小。因此在某些情况下仅是O(1)。它并不总是O(1)。
TamusJRoyce '17

3

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

  • Any() 找到1个项目后立即返回。
  • Count() 必须继续浏览整个列表。

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

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

如果使用谓词替代之一,则可能会更糟-Count()仍必须检查每个项目,即使只有一个匹配项也是如此。

您已经习惯了使用Any Any-它确实有意义并且可读。

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


Any()和Count()之间的区别似乎很明显,但是@crucible的性能分析代码似乎表明,对于IEnumerable <T>的某些实现,Count()更快。对于List <T>,在列表大小增加数千个之前,我无法让Any()给出比Count()更快的结果。LINQ本身必须以某种方式对Count()方法进行一些认真的优化。
马特·汉密尔顿,

3

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

我只能假定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<T>


1

好的,那这个呢?

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

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


3
但是,如果将其正确地封装在一个using块中,它将变得不太简洁,因为否则您将构造一个IDisposable对象然后将其丢弃。然后,当然,当您利用已经存在的扩展方法并将其更改为(恰好做到这一点)时,它变得更加简洁return !enumerable.Any()
丹涛

为什么要重写一个已经存在的方法?如前所述,Any()它确实可以执行此操作,因此,将完全相同的方法添加到另一个名称中只会造成混淆。
Julien N

1

另一个想法:

if(enumerable.FirstOrDefault() != null)

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


3
如果您有一个非空列表,其中第一个元素为null,该怎么办?
Ekevoo 2015年

1

这对于与实体框架一起使用至关重要:

var genericCollection = list as ICollection<T>;

if (genericCollection != null)
{
   //your code 
}

那如何回答这个问题?集合中没有元素时,其值不能为null。
Martin Verjans

0

如果我用Count()检查,Linq在数据库中执行了“ SELECT COUNT(*)..”,但是我需要检查结果是否包含数据,我决定引入FirstOrDefault()而不是Count();。

之前

var cfop = from tabelaCFOPs in ERPDAOManager.GetTable<TabelaCFOPs>()

if (cfop.Count() > 0)
{
    var itemCfop = cfop.First();
    //....
}

var cfop = from tabelaCFOPs in ERPDAOManager.GetTable<TabelaCFOPs>()

var itemCfop = cfop.FirstOrDefault();

if (itemCfop != null)
{
    //....
}

0
private bool NullTest<T>(T[] list, string attribute)

    {
        bool status = false;
        if (list != null)
        {
            int flag = 0;
            var property = GetProperty(list.FirstOrDefault(), attribute);
            foreach (T obj in list)
            {
                if (property.GetValue(obj, null) == null)
                    flag++;
            }
            status = flag == 0 ? true : false;
        }
        return status;
    }


public PropertyInfo GetProperty<T>(T obj, string str)

    {
        Expression<Func<T, string, PropertyInfo>> GetProperty = (TypeObj, Column) => TypeObj.GetType().GetProperty(TypeObj
            .GetType().GetProperties().ToList()
            .Find(property => property.Name
            .ToLower() == Column
            .ToLower()).Name.ToString());
        return GetProperty.Compile()(obj, str);
    }

0

这是我对丹涛的回答的实现,允许一个谓词:

public static bool IsEmpty<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null) throw new ArgumentNullException();
    if (IsCollectionAndEmpty(source)) return true;
    return !source.Any(predicate);
}

public static bool IsEmpty<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) throw new ArgumentNullException();
    if (IsCollectionAndEmpty(source)) return true;
    return !source.Any();
}

private static bool IsCollectionAndEmpty<TSource>(IEnumerable<TSource> source)
{
    var genericCollection = source as ICollection<TSource>;
    if (genericCollection != null) return genericCollection.Count == 0;
    var nonGenericCollection = source as ICollection;
    if (nonGenericCollection != null) return nonGenericCollection.Count == 0;
    return false;
}


-3

myList.ToList().Count == 0。就这样


1
这是一个可怕的主意。ToList()不应过度使用,因为它会强制对可枚举进行全面评估。使用.Any()代替。
乔恩·雷阿2015年

-5

此扩展方法适用于我:

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

5
避免使用此类异常。在上面的代码中,您期望某些定义明确的输入(例如,空枚举)出现异常。因此,它们也不例外,它们是规则。这是对这种控制机制的滥用,它影响了可读性和性能。对于真正例外的情况,保留例外的使用。
康拉德·鲁道夫

通常,我会同意。但这是相应的缺少IsEmpty方法的解决方法。而且我认为,变通办法永远不是做某事的理想方法……此外,尤其是在这种情况下,意图非常明确,“脏”代码被封装并隐藏在定义明确的位置。
强尼·迪

3
-1:如果您想这样做,请使用FirstOrDefault(),如ChulioMartinez的回答。
Daniel Rose 2010年

3
异常处理的性能效率确实很差。因此,这可能是最糟糕的解决方案。
Julien N

“例外应该是例外。” -不要将它们用于正常的程序流程。
乔恩·雷阿2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.