使用 LINQ 检查列表是不是为空

Posted

技术标签:

【中文标题】使用 LINQ 检查列表是不是为空【英文标题】:Checking if a list is empty with LINQ使用 LINQ 检查列表是否为空 【发布时间】:2010-09-07 15:58:55 【问题描述】:

确定列表是否为空的“最佳”(同时考虑速度和可读性)方法是什么?即使列表是 IEnumerable<T> 类型并且没有 Count 属性。

现在我在这之间折腾:

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

还有这个:

if (!myList.Any())  ... 

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

话虽如此,第二个选项对您来说是否可读?你更喜欢哪个?或者您能想出更好的方法来测试空列表吗?

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

public static bool IsEmpty<T>(this IEnumerable<T> list)

    if (list is ICollection<T>) return ((ICollection<T>)list).Count == 0;

    return !list.Any();

【问题讨论】:

最好不要混合使用iscast,而是使用asnull检查:ICollection&lt;T&gt; collection = list as ICollection&lt;T&gt;; if (collection != null) return colllection.Count; 为什么要多写一个方法? list.Any() 不等于list.IsEmpty 吗?框架方法应该被优化——只有当你发现它是一个性能瓶颈时才值得写一个新的。 是否有人费心衡量他们建议的实现的性能,还是每个人都只是提出想法? 我建议向 .NET Core 类库添加 IsEmpty 扩展方法的问题。 github.com/dotnet/corefx/issues/35054如果您喜欢并同意,请查看并投票。 【参考方案1】:

你可以这样做:

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

【讨论】:

或者使用一行并返回 (source==null) ?真:!source.Any(); (如果你没有抛出异常) 我会说,是的,为 null 抛出异常,然后添加第二个扩展方法,称为 IsNullOrEmpty() public static Boolean IsNullOrEmpty(this IEnumerable source) return source == null || !source.Any(); @Gage 如今:return !source?.Any() ?? true; @ricksmt 感谢您的更新!我一定会使用它!【参考方案2】:

我会在您似乎已经确定的代码上做一点补充:还要检查ICollection,因为即使是一些非过时的泛型类也实现了这一点(即Queue&lt;T&gt;Stack&lt;T&gt; )。我也会使用as 而不是is,因为它更习惯用法和has been shown to be faster。

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

【讨论】:

我喜欢这个答案。一个警告是,当某些集合没有完全实现诸如NotSupportedExceptionNotImplementedException 之类的接口时,它们会抛出异常。当我发现我使用的集合引发了 Count 异常时,我第一次使用了您的代码示例(谁知道...)。 我理解为什么这样的优化对于像 Count() 这样需要枚举所有元素的方法很有用。但是 Any() 最多只需要枚举一个元素,所以我在这里看不到重点。另一方面,您添加的强制转换和 if 语句是您必须在每次调用时支付的固定成本。【参考方案3】:

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

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

/编辑@马特:

我只能假设 IEnumerable 的 Count() 扩展方法正在做这样的事情:

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

【讨论】:

【参考方案4】:

我刚刚写了一个快速测试,试试这个:

 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 更慢。

所以我想这取决于您使用的列表类型!

(需要指出,我在 List 中放了 2000 多个对象,计数仍然更快,与其他类型相反)

【讨论】:

Enumerable.Count&lt;T&gt;()ICollection&lt;T&gt; 有特殊处理。如果您尝试使用其他而不是基本列表的内容,我希望您会看到显着不同(较慢)的结果。不过,Any() 将保持不变。 我必须同意马克的观点;这不是一个真正公平的测试。 知道为什么Enumerable.Any&lt;T&gt;()ICollection&lt;T&gt; 没有特殊处理吗?当然,无参数的Any() 也可以检查Count 属性中的ICollection&lt;T&gt; 吗?【参考方案5】:

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

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

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

【讨论】:

如果它是“IEnumerable”,那么没有。 (对于初学者来说,IEnumerable 没有“Count”属性,它有一个 Count() 方法。)。调用“Count()”将要求 IEnumerable 检查列表中的每个元素。而“任何”只要找到 1 个元素就会返回。 取决于数据源。如果你使用 yield 来构建一个 IEnumerable,它必须遍历 IEnumerable 才能知道它的大小。所以在某些情况下它只是 O(1)。它并不总是 O(1)。【参考方案6】:

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

Any() 找到 1 项后立即返回。 Count() 必须继续遍历整个列表。

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

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

如果您使用谓词覆盖之一,情况可能会更糟 - Count() 仍然必须检查每个项目,即使它只有一个匹配项。

您习惯于使用 Any one - 它确实有意义且可读。

一个警告 - 如果您有一个列表,而不仅仅是一个 IEnumerable,那么请使用该列表的 Count 属性。

【讨论】:

Any() 和 Count() 之间的区别似乎很明显,但 @crucible 的分析代码似乎表明 Count() 对于 IEnumerable 的某些实现更快。对于 List 我无法让 Any() 给出比 Count() 更快的结果,直到列表大小达到数千个项目为止。 LINQ 本身必须以某种方式围绕 Count() 方法进行一些认真的优化。【参考方案7】:

@Konrad 令我惊讶的是,在我的测试中,我将列表传递给一个接受 IEnumerable&lt;T&gt; 的方法,因此运行时无法通过调用 IList&lt;T&gt; 的 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&lt;T&gt; 的特殊情况进行了一些运行时优化。

/EDIT @Konrad +1 mate - 你说得对,它更有可能出现在 ICollection&lt;T&gt; 上。

【讨论】:

【参考方案8】:

好的,那这个呢?

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)

    return !enumerable.GetEnumerator().MoveNext();

编辑:我刚刚意识到有人已经草拟了这个解决方案。有人提到 Any() 方法会这样做,但为什么不自己做呢?问候

【讨论】:

但是当你正确地将它包含在一个 using 块中时它变得不那么简洁,因为否则你已经构造了一个 IDisposable 对象然后放弃它。然后,当然,当您使用已经存在的扩展方法并将其更改为 return !enumerable.Any()(正是这样做的)时,它会变得更加简洁。 为什么要重写一个已经存在的方法?如前所述,Any() 正是这样做的,因此使用另一个名称添加完全相同的方法只会令人困惑。【参考方案9】:

另一个想法:

if(enumerable.FirstOrDefault() != null)

不过我更喜欢 Any() 方法。

【讨论】:

如果你有一个第一个元素为空的非空列表怎么办?【参考方案10】:

这对于让它与实体框架一起工作至关重要:

var genericCollection = list as ICollection<T>;

if (genericCollection != null)

   //your code 

【讨论】:

如何回答这个问题?集合中没有元素时不能为空。【参考方案11】:

如果我用 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)

    //....

【讨论】:

【参考方案12】:
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);
    

【讨论】:

【参考方案13】:

这是我对丹涛答案的实现,允许使用谓词:

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;

【讨论】:

【参考方案14】:
List<T> li = new List<T>();
(li.First().DefaultValue.HasValue) ? string.Format("0:yyyy/MM/dd", sender.First().DefaultValue.Value) : string.Empty;

【讨论】:

【参考方案15】:

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

【讨论】:

这是一个糟糕的主意。 ToList() 不应被过度使用,因为它会强制对可枚举进行全面评估。请改用 .Any()。【参考方案16】:

这个扩展方法对我有用:

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)

    try
    
        enumerable.First();
        return false;
    
    catch (InvalidOperationException)
    
        return true;
    

【讨论】:

避免这样使用异常。在上面的代码中,您预期某些定义明确的输入(即空枚举)会出现异常。因此,它们不是例外,它们是规则。这是对这种控制机制的滥用,会影响可读性和性能。为真正的例外情况保留使用例外。 一般来说,我会同意。但这是相应缺失 IsEmpty 方法的解决方法。而且我认为解决方法永远不是做某事的理想方法......此外,特别是在这种情况下,意图非常明确,并且“脏”代码被封装并隐藏在明确定义的位置。跨度> -1:如果您想这样做,请使用 FirstOrDefault(),如 ChulioMartinez 的回答。 异常处理的性能效率真的很差。所以这可能是这里最糟糕的解决方案。 "例外应该是例外。" - 不要将它们用于正常的程序流程。

以上是关于使用 LINQ 检查列表是不是为空的主要内容,如果未能解决你的问题,请参考以下文章

使用 LINQ 检查字符串是不是包含字符串或字符列表

如何使用 Linq where 条件检查字符串列表是不是包含任何字符串

检查列表和数据框是不是为空

如何检查值是不是在列表中或列表是不是为空?

对象的 LINQ 检查列表包含一个值而不是其他一些值

Node.js:如何在不上传文件列表的情况下检查文件夹是不是为空