为什么 Any()不能在 c # null 对象上工作

当对 null 对象调用 任何()时,它在 C # 中抛出 ArgumentNullException。如果对象为 null,则肯定不存在“ any”,并且它可能返回 false。

为什么 C # 会这样?

75500 次浏览

Any方法针对 IEnumerable运行,并告诉您是否有任何项目 在枚举数中。如果您没有给它任何要枚举的内容,那么 ArgumentNullException 是合理的: 没有(匹配)元素的集合与没有集合是不同的。

Any()是一个扩展方法,因此 this实际上是作为第一个参数传递给该方法的。在这种情况下,抛出 ArgumentNullExceptionthisnull是可以理解的。

你可以事先自己进行检查:

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

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

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

如果 Any不是扩展方法,那么在 null上调用它将导致 NullReferenceException。由于 是一种扩展方法,抛出一些异常(尽管不是必需的)是一个好主意,因为它保留了尝试在 null: 砰!上调用方法的众所周知的语义

Any()问: “这个盒子里有什么东西吗?”

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

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


当我想把丢失的集合看作空集合时,我使用以下扩展方法:

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

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

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

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

正如其他人已经提到的,Any检查序列是否包含元素。它不会阻止您传递 null值(首先可能是错误)。

如果 sourcenull,则 Enumerable中的每个扩展方法都抛出一个 ArgumentNullException。在扩展中抛出 ArgumentNullExceptions实际上是 很好的练习

使用现代的 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。这个名字好多了!

Any()是一个扩展方法,如果源为空,则抛出 ArgumentNullException。你会在没有任何东西的情况下做一个动作吗?一般来说,获得代码中发生了什么的显式指示器比获得默认值更好。

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

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

归根结底,这种区别是由于 no 元素和 null 之间的行为差异造成的。

Any()首先在内部检查 source is null是否抛出 ArgumentNullException(在本例中不返回 bool)。否则,它将尝试访问底层序列(IEnumerable)。如果序列中至少存在一个元素,则返回 than true,否则返回 false。这里需要注意的是,从技术上讲有三个返回值,true/false + 抛出一个可以被捕获的 ArgumentNullException(某些可以被捕获的东西,因此从技术上讲是一个返回值)。

首先,如果 source为空,那么它可能听起来只是返回 false,因为在某种程度上可能意味着没有。

然而,这里出现了不同级别的信息和行为。“没有元素”和“空”到底是什么意思?

Any()的行为

  • 如果序列中至少存在一个元素,返回 true
  • 返回 false收藏0元素。
  • 当序列(基础 IEnumable 类型)为空时引发 NullReferenceException

Null 到底是什么意思?

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

TLDR

  • Any()上的集合与 0元素 仍然是一个集合。
  • null不是集合,不是对象,也不是值,什么都不是。

现实世界实例

如果你有一个钱包) ,可以把钱放在里面(潜在的顺序) ,没有现金(元素)在里面,你有一个钱包,但你破产了(没有元素)。零意味着你甚至没有钱包放现金。钱包和现金都不存在。

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

尝试访问不存在的内容会导致程序崩溃,因为这是不可能的。不可能的事情是不能执行的。即使不是所有的语言,也有许多语言是这样的。

正如@msteel 9999所提到的,在任何一种情况下,你都是破产的

在.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();
}
}
}