C#中的List/IEnumerable是否有IsNullOrEmpty方法?

78

我知道通常空列表比NULL更受青睐。但我要返回NULL,主要是出于以下两个原因:

  1. 我必须显式地检查和处理null值,以避免bug和攻击。
  2. 之后执行 ?? 操作很容易得到一个返回值。

对于字符串,我们有IsNullOrEmpty。是否有任何来自C#本身可以做到与List或IEnumerable相同的功能?


7
不过添加一个扩展方法很简单。 - Aaron McIver
可能是重复的问题:如何检查IEnumerable是否为空? - octothorpentine
12个回答

82

这个框架中没有任何内置的内容,但它是一个相当简单直接的扩展方法。

点击此处查看更多信息。

/// <summary>
    /// Determines whether the collection is null or contains no elements.
    /// </summary>
    /// <typeparam name="T">The IEnumerable type.</typeparam>
    /// <param name="enumerable">The enumerable, which may be null or empty.</param>
    /// <returns>
    ///     <c>true</c> if the IEnumerable is null or empty; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsNullOrEmpty<T>(this IEnumerable<T> enumerable)
    {
        if (enumerable == null)
        {
            return true;
        }
        /* If this is a list, use the Count property for efficiency. 
         * The Count property is O(1) while IEnumerable.Count() is O(N). */
        var collection = enumerable as ICollection<T>;
        if (collection != null)
        {
            return collection.Count < 1;
        }
        return !enumerable.Any(); 
    }

为了提高性能,Daniel Vaughan采取了将数据类型转换为ICollection的额外步骤(在可能的情况下)。这是我没有想到的。


5
需要注意的是,调用enumerable.Any()可能会丢失不可重复枚举的元素。您可以将其包装在某些东西中,以跟踪第一个元素(当然要处理null或否定OP的整个问题),但在某些情况下,与空枚举合并并仅使用结果可能更方便。 - Jon Hanna
7
如此所见:http://msdn.microsoft.com/en-us/library/bb337697%28v=vs.110%29.aspx(请查看备注)“只要能够确定结果,源的枚举就会停止。”这使我认为不需要进行到集合的强制转换,因为Enumerable.Any()也应该具有O(1)。那么如果你并不真正知道它是一个列表,为什么要转换成列表呢? - xmoex
3
现在可以简化为一行代码:return a == null || ((a as ICollection)?.Count == 0) || !a.Any(); (意为:如果a为空,或者a是空集合,或者a中没有任何元素,则返回True。) - PRMan
1
我刚意识到这个方案省略了对IReadOnlyCollection(不继承于ICollection)的短路处理。 - Lee Grissom
2
@ElMac ICollection.Count 的规范是跟踪计数,任何实现都应该在不迭代值以计算需求的情况下返回计数。但正如另一个人在评论中所说,同样的优化也应该适用于 IReadOnlyCollection。此外,LINQ.Any 在 ICollection 上使用了优化,但在 IReadOnlyCollection 上没有使用。 - user11523568
显示剩余6条评论

60

最新更新:自C# 6.0以来,可以使用空值传递运算符来表达类似于以下简洁的代码:

if (  list?.Count  > 0 ) // For List<T>
if ( array?.Length > 0 ) // For Array<T>

或者,作为一个更干净、更通用的替代方案,使用 IEnumerable<T>

if ( enumerable?.Any() ?? false )
注意1:所有大写的变量实际上反映的是IsNotNullOrEmpty,与OP问题不同(引用)。

由于运算符优先级,IsNullOrEmpty的等效物看起来不太吸引人:
if (!(list?.Count > 0))

注意2:?? false是必要的,因为以下原因(摘自这篇文章):

?. 运算符如果一个子成员是null,将返回null。 但是[...] 如果我们尝试获取非Nullable成员,比如返回boolAny()方法[...]编译器将会 "wrap" 一个返回值在 Nullable<> 中。例如,Object?.Any() 将给我们bool?(这是Nullable<bool>),而不是bool。 [...] 由于它无法被隐式转换为bool,因此此表达式不能用在if语句中。

注意3:作为奖励,该语句也是“线程安全”的(引自这个问题):

在多线程上下文中,如果[enumerable]可以从另一个线程访问(因为它是可访问的字段,或者因为它是 封闭在对另一个线程可见的lambda中),那么每次计算 [i.e. 在之前的null检查之前] 值可能不同。


@GrimR3 是的,你说得对:那更直接地回答了问题。我这样写是为了与引用/参考保持一致。 - Teodor Tite
你可以使用以下代码检查数组中是否有0或1个元素:(myArray?.Length ?? 0) < 2 - intrepidis

27

没有内置的功能。

但是有一个简单的扩展方法:

public static bool IsNullOrEmpty<T>(this IEnumerable<T> enumerable)
{
  if(enumerable == null)
    return true;

  return !enumerable.Any();
}

1
我想知道为什么这个问题和被采纳的答案都错过了在 Any() 调用之前加上 !。同样的错误也传播到了我的团队源代码中。 - stannius

10
var nullOrEmpty = list == null || !list.Any();

4

将之前的答案整合成一个简单的C# 6.0+扩展方法:

    public static bool IsNullOrEmpty<T>(this IEnumerable<T> me) => !me?.Any() ?? true;

2

正如其他人所说,框架本身并没有包含这个功能,但如果你正在使用Castle的话,Castle.Core.Internal库中有它。

using Castle.Core.Internal;

namespace PhoneNumbers
{
    public class PhoneNumberService : IPhoneNumberService
    {
        public void ConsolidateNumbers(Account accountRequest)
        {
            if (accountRequest.Addresses.IsNullOrEmpty()) // Addresses is List<T>
            {
                return;
            }
            ...

1
如果您需要在情况不为空的情况下检索所有元素,则这里的某些答案将无法工作,因为对于不可倒带的可枚举对象调用Any()将“忘记”一个元素。
您可以采取不同的方法,将null转换为空值:
bool didSomething = false;
foreach(var element in someEnumeration ?? Enumerable.Empty<MyType>())
{
  //some sensible thing to do on element...
  didSomething = true;
}
if(!didSomething)
{
  //handle the fact that it was null or empty (without caring which).
}

同样地,可以使用 (someEnumeration ?? Enumerable.Empty<MyType>()).ToList() 等语句。

你可以给我一个 .NET 中不可回溯集合的例子吗?我的意思是指现有框架中的类名,而不是要求你为其提供实现示例。 - AaronLS
@AaronLS 大多数 Linq 查询的结果。 - Jon Hanna
在这种情况下,具体类型会是什么?我知道IQueryable并不是真正的列表,直到你尝试ToList/foreach时才会实现它。我想象一下,在IQueryable中有一些中间的具体类型,在幕后是一个只向前的数据读取器,所以你说的话是有道理的,只是我无法想象你在哪里真正遇到这个问题。如果你foreach(var item in someQueryable)将生成自己的枚举器,然后执行someQueryable.Any()将是一个单独的查询,并不会影响当前的foreach枚举器AFAIK。 - AaronLS
不是挑战你,只是想解释一下我的困惑。 - AaronLS
@AaronLS 这将是一个单独的枚举器,这将要求您再次运行查询,这已经够糟糕了。如果它被转换为IEnumerable,那么情况就更糟了,因为查询将会丢失。 - Jon Hanna

0
var nullOrEmpty = !( list?.Count > 0 );

2
仅仅提供代码并不能算是好的回答,尝试加入一些解释性语句来说明问题所在及代码如何解决问题。 - MikeT
如果列表是IEnumerable,这将无法编译。 - Lee Grissom

0

我修改了Matthew Vines的建议,以避免“可能多次枚举IEnumerable”的问题。(另请参阅Jon Hanna的评论)

public static bool IsNullOrEmpty(this IEnumerable items)
    => items == null
    || (items as ICollection)?.Count == 0
    || !items.GetEnumerator().MoveNext();

...以及单元测试:

[Test]
public void TestEnumerableEx()
{
    List<int> list = null;
    Assert.IsTrue(list.IsNullOrEmpty());

    list = new List<int>();
    Assert.IsTrue(list.IsNullOrEmpty());

    list.AddRange(new []{1, 2, 3});
    Assert.IsFalse(list.IsNullOrEmpty());

    var enumerator = list.GetEnumerator();
    for(var i = 1; i <= list.Count; i++)
    {
        Assert.IsFalse(list.IsNullOrEmpty());
        Assert.IsTrue(enumerator.MoveNext());
        Assert.AreEqual(i, enumerator.Current);
    }

    Assert.IsFalse(list.IsNullOrEmpty());
    Assert.IsFalse(enumerator.MoveNext());
}

0

带有可空支持的一行代码:

public static bool IsNullOrEmpty<T>(this IEnumerable<T>? enumerable) => 
            enumerable == null || !enumerable.Any();

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