C# 多次枚举和使用Any()

C# 多次枚举和使用Any(),c#,.net,algorithm,linq,C#,.net,Algorithm,Linq,当我需要做下面的事情时,我试图找出什么是LINQ的合适约定 如果有项目,请逐行打印 如果没有项目,请打印“无项目” 我认为这样做就像 if (items.Any()) { foreach (string item in items) { Console.WriteLine(item); } } else { Console.WriteLine("No items"); } 然而,这在技术上违反了多重枚举的原则。一种不违反这一点的方法是 boo

当我需要做下面的事情时,我试图找出什么是LINQ的合适约定

  • 如果有项目,请逐行打印
  • 如果没有项目,请打印
    “无项目”
我认为这样做就像

if (items.Any())
{
    foreach (string item in items)
    {
        Console.WriteLine(item);
    }
}
else
{
    Console.WriteLine("No items");
}
然而,这在技术上违反了多重枚举的原则。一种不违反这一点的方法是

bool any = false;
foreach (string item in items)
{
    any = true;
    Console.WriteLine(item);
}   
if (!any)
{
    Console.WriteLine("No items");
}
但很明显,这并不那么优雅

var itemsList=列表中的项目??items.ToList();
var itemsList = items as List<Item> ?? items.ToList();

如果
IEnumerable
已经是一个列表,那么您已经设置好了。如果没有,则调用
.ToList()
。无论哪种方式,你最终都会得到一份清单。(Resharper默认为数组。我显示的是一个列表,与一些注释不同。)

这可能是个问题,也可能不是问题,这取决于您的用例,因为只要满足条件,Any()就会短路,这意味着不需要枚举整个IEnumerable

请注意下面的评论,这些评论指出了潜在的缺陷,例如实施只向前推进或成本高昂

以下是:

公共静态bool Any(此IEnumerable源代码){
if(source==null)抛出错误.ArgumentNull(“source”);
使用(IEnumerator e=source.GetEnumerator()){
如果(e.MoveNext())返回true;
}
返回false;
}

既然我们讨论的是LINQ,那么一个非常LINQ的解决方案怎么样

foreach (var item in items.DefaultIfEmpty("No items"))
    Console.WriteLine(item);

你可以只
.ToList()
IEnumerable,检查Count,然后在Count>0时foreach。@maccettura这样做有什么用?我可能会
.ToList()
,但老实说,我看不出你的“不那么优雅”有什么不好的地方解决方案。这看起来就像是在没有明显性能问题的情况下进行优化的尝试。与第二个示例相比,您的第一个示例更具可读性和可维护性。@ErikPhilips我将此问题理解为根据特定指南关注正确性,而不是性能。该指导方针有多种原因,绩效是其中之一,但不是唯一的原因。在这里,编译器不允许避免多次枚举。这将违反C语言规范。因此,您关于信任编译器来处理它的评论在这里是非常错误的。empty ienumerable!=null这是低效的,因为它必须复制
IEnumerable
的全部内容,如果
是无界的,这可能是不需要的
ToArray
ToList
@Fredou-“empty ienumerable!=null”相比,可以触发双重复制。对。但是一个空的IEnumerable(它不是一个使用
as
转换为数组的数组)是空的。如果实现是一个只向前的,并且执行一个操作或者代价很高,该怎么办?任何()都可能是problematic@StephenKennedy当然会的。但是第一个枚举只是第一项。Resharper警告不要这样做,因为你不知道第二个
GetEnumerator
调用(序列不是空的)会有多昂贵这确实是个问题。这不仅是因为即使是
.Any()
也可能很昂贵,还因为枚举两次可能会产生不同的结果。太多的假设与实际问题无关。没有明确的性能问题。没有指定的自定义列表类型。太多的垃圾评论了。@TravisJ实际上,
Any
不会迭代整个序列。但它确实迭代了第一项。如果以后对它进行
foreach
,那么现在已经对源序列进行了两次迭代。这会引起各种各样的问题。迭代源序列(即使只是为了得到第一个项)可能会产生副作用,成本可能会高得让人望而却步(许多序列在检索到第一个项之前需要做很多工作),在后续迭代时可能会产生不同的结果,或者其他任何可能的结果。如果是这种情况,并且您知道序列仅通过复杂的操作生成,那么应该保证它有条目。此外,在这种极其复杂的可枚举情况下,它很可能是一个自定义实现,并且应该通过缓存自己的枚举器来保护自己不受这种情况的影响。虽然这种方法确实是一种有效的方法,但它并不是一种宏大的改进。@TravisJ问题是如何在不重复源序列两次的情况下解决他们的问题。这大概是因为他们知道他们有一个不能多次迭代的序列。这种复杂的情况可能不涉及任何自定义迭代器。涉及这些类型序列的最常见情况是使用任何LINQ查询提供程序与数据库交互时。如果您觉得EF(或基本上所有其他查询提供程序)由于没有缓存每个查询而实现错误,请向@TravisJ女士提及。上下文缓存行对象,我建议这与查询结果不同。@TravisJ和NetMage刚刚告诉您的一样,缓存对象并不意味着缓存查询或查询结果。它不会缓存查询结果。每次迭代源序列时,它都会对数据库执行查询并获得更新的结果。它可能会重新使用这些结果中的某些对象,而不是重新创建它们,但每次都会执行查询。
foreach (var item in items.DefaultIfEmpty("No items"))
    Console.WriteLine(item);