为什么在c#中,对于一个null对象,Any()方法无法正常工作?

83

在C#中,当对一个空对象调用Any()方法时,会抛出一个ArgumentNullException异常。如果对象为空,则显然没有“任何”东西,应该返回false。

为什么C#会这样表现呢?


7
"null"和空序列是两个不同的概念。 - CodesInChaos
2
这个问题提供了一些有用的方式来实现空值检查,如果你觉得在使用.Any()之前手动进行检查很笨拙。 - Thomas Mulder
我知道这是一个古老的问题,但我喜欢你的用户名!哈哈 - Andrew Steitz
10个回答

177

Any()的意思是:“这个盒子里有没有物品?”

如果盒子是空的,答案显然是否定的。

但是如果一开始就没有盒子,那么这个问题就没有意义,该函数会抱怨:“你在说什么东西?根本就没有盒子。”


当我想将一个缺失的集合视为一个空集合时,我使用以下扩展方法:

public static IEnumerable<T> OrEmpty<T>(this IEnumerable<T> sequence)
{
    return sequence ?? Enumerable.Empty<T>();
}

这可以与所有LINQ方法和foreach结合使用,而不仅仅是.Any()


41
没有勺子。 - Jesper Bischoff-Jensen
1
所以我想知道为什么没有一个AnyOrDefault选项来处理“没有勺子”的情况... - Dscoduc
4
你可以使用?.Any() ?? false或者?.Any() == true。另外,如果你想使用我答案中提供的扩展方法,可以使用.OrEmpty().Any() - CodesInChaos
1
@Dscoduc 我只是使用这个扩展来检查是否为空或null public static bool IsNullOrEmpty<T>(this IEnumerable<T> list) => list == null || !list.Any(); - azurez27
2
这就是问题的关键。我所参与的每个项目(大多数)都有类似的扩展方法。为什么不内置NotNull()或NotNullOrAny()方法呢? - msteel9999
看我的回答,我找到了我们正在寻找的方法,它已经在 .Net Framework 中了。 - Jeremy Thompson

80

使用现代的 C#,您可以通过像这样简单的检查轻松处理 OP 的情况:

List<string> foo = null;

if (foo?.Any() ?? false)
{
    DoStuff();
}

这有点像一个简单的 AnyOrDefault(bool default) 实现,OP 希望 Any() 扩展方法能够做到这一点。

你可以很容易地将其转换为扩展方法如下:

public static bool HasItems<T>(this IEnumerable<T> source)
{
    return (source?.Any() ?? false);
}

说实话,我不太喜欢这个名字AnyOrDefault,因为永远不会有意义地传递一个默认值(默认为true可能对以后阅读代码的人很不友好)。根据评论建议改名为HasItems。这是一个更好的名称!


1
这是一个很好的解决方案,但如果你经常这样做,编写自己的扩展方法来执行此操作会更清晰,特别是如果你将其嵌入到三元表达式或类似结构中。 - Brandon Barkley
6
public static bool IsNullOrEmpty<T>(this IEnumerable<T> source) { return source?.Any() != true; } 相似于 String.IsNullOrEmpty(stringything) - Jesse C. Slicer
4
个人意见:我个人认为 if (foo?.Any() == true).. ?? false) 更容易理解。前者在英语中的意思是 "如果有任何一个为真,则执行…",而后者本质上是一个两步逻辑语句,更难以用简单的话来表述。它的意思类似于 "如果有任何一个(但将 null 视为 false)为真,则执行…"。 - ToolmakerSteve
2
AnyOrDefault 的替代名称可能是 HasItems 或更明确的 AnyAndNotNull。另外,"Default" 在你的 AnyOrDefault 中指的是源类型的默认值,例如 IEnumerable 的默认值为 "null",与布尔值的默认值 "false" 无关。因此,这个名称是具有误导性的,因为 AnyOrDefault 在逻辑上应该意味着 "任何一个或 null(因为那是默认值)",所以会对 null 返回 true。显然这不是你的意思。 - ToolmakerSteve
现在应该称之为 HasItems(),无论是空列表还是null,名称仍然正确。 - Pawel Cioch

58

处理引用类型时,null 值在语义上与“空”值不同。

null 字符串与 string.Empty 不同,而 nullIEnumerable<T> 也不同于 Enumerable.Empty<T>(或该类型的任何其他“空”枚举)。

如果 Any 不是扩展方法,则在 null 上调用它将导致 NullReferenceException。因为它是扩展方法,所以抛出一些异常(虽然不是必需的)是一个好主意,因为它保留了尝试在 null 上调用方法的众所周知的语义:BOOM!


值得一提的是,为什么区分空集合和未初始化的集合很重要。我能想到的唯一原因是它可以告诉你集合是否占用了内存空间,但我并不是C#大师。 - ocket8888
8
虽然这是正确的,但我认为这些术语已过时。通常,人们希望通过“Any()”、“Where()”、“Select()”等调用或甚至是“foreach”迭代知道是否有任何内容,并在那里进行操作。许多代码浪费存在于无意义的实例化或空值检查中。我们已经拥有了?.操作符。请全力以赴,为扩展方法提供良好的空值功能。 - Brandon Barkley
帮助无助的程序员是个非常糟糕的想法...需要使用一个笨拙的(Source?.Any()).GetValueOrDefault()来进行简单的空值检查或者在strict on场景下判断不存在性...这是Linq团队的一个糟糕决定:他们应该时刻记住,他们的东西是为那些知道自己在做什么的人而设计的,而不是像我这样毫无头绪的人! :/ - Shockwaver

6

Any() 是一个扩展方法,所以 this 实际上被作为第一个参数传递给该方法。在这种情况下,如果 thisnull,那么抛出 ArgumentNullException 是可以理解的。

您可以事先进行检查:

bool hasAny = yourData == null ? false : yourData.Any(yourPredicate);

3

因为Any()是一个像这样的扩展方法:

public static bool Any(this IEnumerable enumerable)
{
    if (enumerable == null)
        throw ArgumentNullException("enumerable");
    ...
}

3
Any方法针对IEnumerable运行,告诉您在可枚举集合中是否存在任何项。如果您没有给它任何要枚举的内容,则引发ArgumentNullException异常:不包含(匹配的)元素的集合与没有集合不同。请注意保留HTML标记。

1

归根结底,区别在于没有元素和null之间的行为差异。

Any()首先内部检查source是否为null,如果是,则抛出ArgumentNullException(在这种情况下不返回bool)。否则,它尝试访问基础序列(IEnumerable)。如果序列中至少存在一个元素,则返回true,否则返回false。需要注意的是,在技术上有三个返回值,true/false + 抛出可以捕获的ArgumentNullException(在技术上也算一种返回)。

起初,如果source为null,似乎应该只返回false,因为从某种意义上来说可能表示没有元素。

然而,这里涉及到不同级别的信息和行为。没有元素和null真正意味着什么?

Any()的行为

  • 如果至少有一个元素存在于序列中,则返回true
  • 当集合中没有元素时,返回false
  • 当序列(基础的IEnumerable类型)为null时,抛出NullReferenceException异常。

null

的真实含义是什么?

null 在所有情境下都表示什么都没有;没有数据,没有内存指针。 null 不是一个集合,也不是一个对象。它代表了内存中的缺失。当计算机“读取内存”时,它试图从特定位置读取存储在内存中的值。如果内存读取操作遇到特殊的空字符,即“空终止符”\0,CPU 就知道它无法读取内存中的这部分内容。Null 还意味着没有内存来描述某种对象或值。在您的示例中,您调用 Any() 的基础 IEnumerable 对象不存在,只有一个空终止符 \0。尝试读取仅包含空终止符的内存地址将引发致命异常。如果未处理,NullReferenceException 将按照操作系统或其他软件层(位于您运行的应用程序正下方)定义的方式终止每个程序。

简而言之

  • 对于具有 0 个元素的集合执行 Any() 仍然是一个集合。
  • null 不是集合、对象或值。只是什么都没有。

实际的现实世界例子

如果你有一个可以装钱的钱包(基础序列),但里面没有现金(元素),那么你有一个钱包,但是你很穷(没有元素)。Null 意味着你甚至没有一个可以放现金的钱包。钱包和现金都不存在。

最后,为 null 的这种行为辩护

试图访问不存在的东西应该会导致程序崩溃,因为这是不可能的。不可能的事情无法执行。在许多语言中,这是正确的。

正如 @msteel9999 所提到的,在任何一种情况下,你仍然很穷。:D


如果你没有钱包,你仍然是一贫如洗的 :) - msteel9999

1

正如其他人已经提到的那样,Any检查序列是否包含元素。它不会阻止您传递null值(这可能是第一次出现错误)。

Enumerable中的每个扩展方法都会在sourcenull时抛出ArgumentNullException。在扩展中抛出ArgumentNullExceptions实际上是良好的做法


0

在 .Net Framework 中有一个方法可以实现这个,为什么他们把它藏在这里,谁知道呢...

namespace Microsoft.IdentityModel.Tokens
{
    /// <summary>
    /// A class which contains useful methods for processing collections.
    /// </summary>
    public static class CollectionUtilities
    {
        /// <summary>
        /// Checks whether <paramref name="enumerable"/> is null or empty.
        /// </summary>
        /// <typeparam name="T">The type of the <paramref name="enumerable"/>.</typeparam>
        /// <param name="enumerable">The <see cref="IEnumerable{T}"/> to be checked.</param>
        /// <returns>True if <paramref name="enumerable"/> is null or empty, false otherwise.</returns>
        public static bool IsNullOrEmpty<T>(this IEnumerable<T> enumerable)
        {
            return enumerable == null || !enumerable.Any();
        }
    }
}

0

Any() 是一个扩展方法,如果源为空,则会抛出 ArgumentNullException。你会对空值执行操作吗?一般来说,在代码中获得一些明确的指示比默认值更好。

但这并不意味着它不能这样。如果你知道自己在做什么,可以编写自己的自定义实现。

我只是想与您分享我们公司正在遵循的一些实用建议。我们编写了自定义包,共享私有 NuGet,这些包广泛用于我们的产品中。检查列表是否为空非常频繁,因此我们决定编写自己的 Any 实现,使我们的代码更短、更简单。


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