使用 !=null、Count > 0 和 .Any() 时的 C# 最佳实践
C# Best Practices when using !=null, Count > 0, and .Any()
出于好奇,!=null
、Count > 0
和 .Any()
之间的根本区别是什么?使用它们的最佳时间是什么时候? - 对于架构和性能。
我知道 .Any()
用于 IEnumerable
,而不是列表,但我发现自己在允许的情况下可以互换使用它们 (!=null and Count > 0
)。如果这是不好的做法,我不想养成习惯。
这取决于你的目标。一个List
可以不为null,但是有Count == 0
。 Any()
怎么样 - 在 LINQ 风格中使用它,例如:
if(MyEnumerable.Any(x => x.Id == myId))
{
// do something
}
!=null
检查列表是否为空。这可能代表也可能不代表与“无项目”相同的事物。我建议完全避免对列表使用 null,因为含义不明确。使用空集合来表示“没有项目”。如果您需要其他东西来表示“不存在”,请使用 maybe/optional 类型,或者如果它们默认为 non-nullable 则可能为空引用。这应该是向 reader 发出的更明确的信号,即“无项目”和“不存在”是可能需要以不同方式处理的不同状态。
.Any()
这是 LINQ 的一部分,因此适用于任何可枚举集合。很清楚也很方便,但是会比查一个属性稍微慢一点。但如果您想检查某些特定值的存在,它允许内联过滤。
.Count > 0 or .Length > 0
这仅适用于具有 Count/Length 属性 的集合,但应该是最快的,因为它只是进行比较。至少假设属性很简单,不会做很多工作。
但请注意,除非您 运行 代码 非常 频繁,否则性能问题不是很重要。因此,在尝试进行任何优化之前先测量性能。
Enumerable.Any
的工作方式如下
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
throw Error.ArgumentNull(nameof (source));
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
// Returns very early if there are any items
if (enumerator.MoveNext())
return true;
}
return false;
}
有一些开销,但实际上并没有那么多,因此我假设 Any()
的性能与 List<T>.Count
相当(IEnumerable
扩展试图回退到这个值,因此这也应该是可比较的)。
Clarity-wise,最终归结为品味问题。我个人喜欢 Any()
的表现力,因为它对我来说更接近自然语言。
rows.Any()
让我想不到
rows.Count == 0
但没那么多。
null
比较以完全不同的方式工作。如果您不清楚这一点,您应该了解 C# 中的值和引用类型(警告:最近情况有所不同,因为值类型不允许 null
在当前版本的 C#/.NET 中默认。
总结:引用类型的存储方式不同,实际变量(某种程度上)只是指向数据存储位置的指针(并非 100% 准确)。 C++ 中的经典指针可以采用值 0
来指示没有它们指向的数据,这被转移(至少作为一个概念)到 C#(和其他 C-like 语言作为 Java).无论如何,由于在 C# 中不能直接访问指针,因此发明了 null
来指示变量不指向实际数据(“没有与该变量关联的数据”)。因此,将 List<int>
变量与 null
进行比较意味着您询问列表实例是否已经创建,这是必要的,但对于存储的实际列表项来说并不是一个充分的标准。
TL;DR:Any()
实际上会调用 ICollection.Count
,如果集合类型实现了它。否则它将尝试选择“最佳”方法来检查集合是否为 non-empty.
检查 null
与检查空值有很大不同,将它与空值检查进行比较没有任何意义。
Paul Kertscher 已经回答了您的问题的实质,但是为了帮助将来查询此类问题,值得记住的是,事实上,dotnet 的源代码可以在线获得。
例如,LINQ Any()
方法的代码可在此处找到:https://github.com/dotnet/runtime/blob/main/src/libraries/System.Linq/src/System/Linq/AnyAll.cs (and there's plenty more in the rest of the runtime repo, with the libraries here)。它比 Paul 在他的回答中包含的方法版本稍微复杂一些……他可能引用了不同版本的运行时。不过,这是最新的实现(原始来源中的评论):
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
if (source is ICollection<TSource> collectionoft)
{
return collectionoft.Count != 0;
}
else if (source is IIListProvider<TSource> listProv)
{
// Note that this check differs from the corresponding check in
// Count (whereas otherwise this method parallels it). If the count
// can't be retrieved cheaply, that likely means we'd need to iterate
// through the entire sequence in order to get the count, and in that
// case, we'll generally be better off falling through to the logic
// below that only enumerates at most a single element.
int count = listProv.GetCount(onlyIfCheap: true);
if (count >= 0)
{
return count != 0;
}
}
else if (source is ICollection collection)
{
return collection.Count != 0;
}
using (IEnumerator<TSource> e = source.GetEnumerator())
{
return e.MoveNext();
}
}
ICollection
的代码是 System.Collection, and IListProvider
is part of System.Linq 的一部分。
您可以看到 Any()
可以通过多种方式来测试基础集合中是否包含任何项目,并且您可以合理地假设它们是按 speed/efficiency 的顺序进行尝试的。显然,您 可以 编写 ICollection.Count { get }
的一个非常糟糕的实现,但那将是一件愚蠢的事情,并且很高兴认为微软在任何时候都没有这样做dotnet 运行时附带的集合类型。 (顺便说一句,arrays 通过显式接口实现 ICollection.Count
,将其映射到它们的 Length
属性,这就是为什么 Any()
不需要关心检查 Length
)
我还没有查找 IListProvider<T>.GetCount
的任何实现。接口方法的文档是这样说的:
If true then the count should only be calculated if doing so is quick (sure or likely to be constant time), otherwise -1 should be returned.
同样,糟糕的实现可能存在,但可能不存在。
使用 Any()
而不是 Count
通常没有任何问题。通过一些 ifs 和强制转换调用方法和 运行 的额外开销通常可以忽略不计。只需确保不要将 Count
与 Count()
混为一谈,因为后者可能需要遍历整个集合,这可能是一件相对昂贵且耗时的事情,而且如果你只是想要做的是检查是否为空。
出于好奇,!=null
、Count > 0
和 .Any()
之间的根本区别是什么?使用它们的最佳时间是什么时候? - 对于架构和性能。
我知道 .Any()
用于 IEnumerable
,而不是列表,但我发现自己在允许的情况下可以互换使用它们 (!=null and Count > 0
)。如果这是不好的做法,我不想养成习惯。
这取决于你的目标。一个List
可以不为null,但是有Count == 0
。 Any()
怎么样 - 在 LINQ 风格中使用它,例如:
if(MyEnumerable.Any(x => x.Id == myId))
{
// do something
}
!=null
检查列表是否为空。这可能代表也可能不代表与“无项目”相同的事物。我建议完全避免对列表使用 null,因为含义不明确。使用空集合来表示“没有项目”。如果您需要其他东西来表示“不存在”,请使用 maybe/optional 类型,或者如果它们默认为 non-nullable 则可能为空引用。这应该是向 reader 发出的更明确的信号,即“无项目”和“不存在”是可能需要以不同方式处理的不同状态。
.Any()
这是 LINQ 的一部分,因此适用于任何可枚举集合。很清楚也很方便,但是会比查一个属性稍微慢一点。但如果您想检查某些特定值的存在,它允许内联过滤。
.Count > 0 or .Length > 0
这仅适用于具有 Count/Length 属性 的集合,但应该是最快的,因为它只是进行比较。至少假设属性很简单,不会做很多工作。
但请注意,除非您 运行 代码 非常 频繁,否则性能问题不是很重要。因此,在尝试进行任何优化之前先测量性能。
Enumerable.Any
的工作方式如下
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
throw Error.ArgumentNull(nameof (source));
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
// Returns very early if there are any items
if (enumerator.MoveNext())
return true;
}
return false;
}
有一些开销,但实际上并没有那么多,因此我假设 Any()
的性能与 List<T>.Count
相当(IEnumerable
扩展试图回退到这个值,因此这也应该是可比较的)。
Clarity-wise,最终归结为品味问题。我个人喜欢 Any()
的表现力,因为它对我来说更接近自然语言。
rows.Any()
让我想不到
rows.Count == 0
但没那么多。
null
比较以完全不同的方式工作。如果您不清楚这一点,您应该了解 C# 中的值和引用类型(警告:最近情况有所不同,因为值类型不允许 null
在当前版本的 C#/.NET 中默认。
总结:引用类型的存储方式不同,实际变量(某种程度上)只是指向数据存储位置的指针(并非 100% 准确)。 C++ 中的经典指针可以采用值 0
来指示没有它们指向的数据,这被转移(至少作为一个概念)到 C#(和其他 C-like 语言作为 Java).无论如何,由于在 C# 中不能直接访问指针,因此发明了 null
来指示变量不指向实际数据(“没有与该变量关联的数据”)。因此,将 List<int>
变量与 null
进行比较意味着您询问列表实例是否已经创建,这是必要的,但对于存储的实际列表项来说并不是一个充分的标准。
TL;DR:Any()
实际上会调用 ICollection.Count
,如果集合类型实现了它。否则它将尝试选择“最佳”方法来检查集合是否为 non-empty.
检查 null
与检查空值有很大不同,将它与空值检查进行比较没有任何意义。
Paul Kertscher 已经回答了您的问题的实质,但是为了帮助将来查询此类问题,值得记住的是,事实上,dotnet 的源代码可以在线获得。
例如,LINQ Any()
方法的代码可在此处找到:https://github.com/dotnet/runtime/blob/main/src/libraries/System.Linq/src/System/Linq/AnyAll.cs (and there's plenty more in the rest of the runtime repo, with the libraries here)。它比 Paul 在他的回答中包含的方法版本稍微复杂一些……他可能引用了不同版本的运行时。不过,这是最新的实现(原始来源中的评论):
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
if (source is ICollection<TSource> collectionoft)
{
return collectionoft.Count != 0;
}
else if (source is IIListProvider<TSource> listProv)
{
// Note that this check differs from the corresponding check in
// Count (whereas otherwise this method parallels it). If the count
// can't be retrieved cheaply, that likely means we'd need to iterate
// through the entire sequence in order to get the count, and in that
// case, we'll generally be better off falling through to the logic
// below that only enumerates at most a single element.
int count = listProv.GetCount(onlyIfCheap: true);
if (count >= 0)
{
return count != 0;
}
}
else if (source is ICollection collection)
{
return collection.Count != 0;
}
using (IEnumerator<TSource> e = source.GetEnumerator())
{
return e.MoveNext();
}
}
ICollection
的代码是 System.Collection, and IListProvider
is part of System.Linq 的一部分。
您可以看到 Any()
可以通过多种方式来测试基础集合中是否包含任何项目,并且您可以合理地假设它们是按 speed/efficiency 的顺序进行尝试的。显然,您 可以 编写 ICollection.Count { get }
的一个非常糟糕的实现,但那将是一件愚蠢的事情,并且很高兴认为微软在任何时候都没有这样做dotnet 运行时附带的集合类型。 (顺便说一句,arrays 通过显式接口实现 ICollection.Count
,将其映射到它们的 Length
属性,这就是为什么 Any()
不需要关心检查 Length
)
我还没有查找 IListProvider<T>.GetCount
的任何实现。接口方法的文档是这样说的:
If true then the count should only be calculated if doing so is quick (sure or likely to be constant time), otherwise -1 should be returned.
同样,糟糕的实现可能存在,但可能不存在。
使用 Any()
而不是 Count
通常没有任何问题。通过一些 ifs 和强制转换调用方法和 运行 的额外开销通常可以忽略不计。只需确保不要将 Count
与 Count()
混为一谈,因为后者可能需要遍历整个集合,这可能是一件相对昂贵且耗时的事情,而且如果你只是想要做的是检查是否为空。