从 IEnumable < T > 中计数项而不进行迭代?

private IEnumerable<string> Tables
{
get
{
yield return "Foo";
yield return "Bar";
}
}

假设我希望对这些进行迭代,并编写类似于处理 # n of # m 的代码。

有没有一种方法可以不用在主迭代之前进行迭代就可以求出 m 的值?

我希望我说得够清楚了。

461917 次浏览

不,不一般。使用枚举对象的一点是,枚举中的实际对象集是不知道的(预先知道,甚至根本不知道)。

IEnumerable不支持这个。这是有意为之。IEnumerable使用惰性求值在你需要元素之前获取它们。

如果你想知道项目的数量而不遍历它们,你可以使用ICollection<T>,它有一个Count属性。

我的一个朋友有一系列的博客文章来说明为什么你不能这样做。他创建了一个返回IEnumerable的函数,在这个函数中,每次迭代都会返回下一个质数,一直到ulong.MaxValue,直到你请求它,下一项才会被计算出来。一个简单的问题:返回了多少项?

以下是一些帖子,但它们有点长:

  1. 外循环(提供一个在其他帖子中使用的初始EnumerableUtility类)
  2. 迭代的应用(初始实现)
  3. 疯狂的扩展方法:ToLazyList(性能优化)

IEnumerable不迭代就不能计数。

在“正常”情况下,实现IEnumerable或IEnumerable<T>的类(如List<T>)可以通过返回List<T>来实现Count方法。数属性。然而,Count方法实际上并不是定义在IEnumerable<T>或IEnumerable接口。(事实上,唯一一个是GetEnumerator。)这意味着不能为它提供特定于类的实现。

相反,Count是一个扩展方法,定义在静态类Enumerable上。这意味着它可以在IEnumerable<T>派生类,而不考虑该类的实现。但这也意味着它是在一个单独的地方实现的,在任何这些类的外部。这当然意味着它必须以一种完全独立于这些类内部的方式实现。计数的唯一方法是通过迭代。

只是增加了一些额外的信息:

Count()扩展并不总是迭代。考虑Linq to Sql,其中计数进入数据库,但不是返回所有行,而是发出Sql Count()命令并返回该结果。

此外,编译器(或运行时)足够聪明,如果它有对象的Count()方法,它会调用它。所以它是,正如其他响应者所说,完全无知,总是迭代以计数元素。

在许多情况下,程序员只是使用Any()扩展方法检查if( enumerable.Count != 0 ),就像在if( enumerable.Any() )中一样,它使用linq的惰性求值要有效得多,因为一旦确定有任何元素,它就会短路。它的可读性也更强

我建议你打电话给ToList。是的,您正在提前进行枚举,但您仍然可以访问项目列表。

你也可以这样做:

Tables.ToList<string>().Count;

这里有一个关于懒惰的评价延迟执行的很棒的讨论。基本上你必须物化这个列表才能得到这个值。

System.Linq.Enumerable.Count扩展方法在IEnumerable<T>上有以下实现:

ICollection<T> c = source as ICollection<TSource>;
if (c != null)
return c.Count;


int result = 0;
using (IEnumerator<T> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
result++;
}
return result;

因此,它尝试强制转换为ICollection<T>,它具有Count属性,并尽可能使用该属性。否则它会迭代。

所以你最好的选择是在你的IEnumerable<T>对象上使用Count()扩展方法,因为这样你会得到最好的性能。

超越你的直接问题(已经被彻底否定地回答了),如果你在处理一个可枚举对象时希望报告进展,你可能想看看我的博客文章在Linq查询期间报告进度

它让你这样做:

BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += (sender, e) =>
{
// pretend we have a collection of
// items to process
var items = 1.To(1000);
items
.WithProgressReporting(progress => worker.ReportProgress(progress))
.ForEach(item => Thread.Sleep(10)); // simulate some real work
};

IEnumerable.Count()函数的结果可能是错误的。这是一个非常简单的测试样本:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections;


namespace Test
{
class Program
{
static void Main(string[] args)
{
var test = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
var result = test.Split(7);
int cnt = 0;


foreach (IEnumerable<int> chunk in result)
{
cnt = chunk.Count();
Console.WriteLine(cnt);
}
cnt = result.Count();
Console.WriteLine(cnt);
Console.ReadLine();
}
}


static class LinqExt
{
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int chunkLength)
{
if (chunkLength <= 0)
throw new ArgumentOutOfRangeException("chunkLength", "chunkLength must be greater than 0");


IEnumerable<T> result = null;
using (IEnumerator<T> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
{
result = GetChunk(enumerator, chunkLength);
yield return result;
}
}
}


static IEnumerable<T> GetChunk<T>(IEnumerator<T> source, int chunkLength)
{
int x = chunkLength;
do
yield return source.Current;
while (--x > 0 && source.MoveNext());
}
}
}

结果必须是(7,7,3,3),但实际结果是(7,7,3,17)

这取决于。net的版本和IEnumerable对象的实现。 微软已经修复了IEnumerable。方法来检查实现,并使用ICollection。计数或收集;TSource >。计数,详见https://connect.microsoft.com/VisualStudio/feedback/details/454130

下面是来自Ildasm for System的MSIL。核心,系统在其中。Linq驻留。

.method public hidebysig static int32  Count<TSource>(class 


[mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource> source) cil managed
{
.custom instance void System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
// Code size       85 (0x55)
.maxstack  2
.locals init (class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource> V_0,
class [mscorlib]System.Collections.ICollection V_1,
int32 V_2,
class [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource> V_3)
IL_0000:  ldarg.0
IL_0001:  brtrue.s   IL_000e
IL_0003:  ldstr      "source"
IL_0008:  call       class [mscorlib]System.Exception System.Linq.Error::ArgumentNull(string)
IL_000d:  throw
IL_000e:  ldarg.0
IL_000f:  isinst     class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource>
IL_0014:  stloc.0
IL_0015:  ldloc.0
IL_0016:  brfalse.s  IL_001f
IL_0018:  ldloc.0
IL_0019:  callvirt   instance int32 class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource>::get_Count()
IL_001e:  ret
IL_001f:  ldarg.0
IL_0020:  isinst     [mscorlib]System.Collections.ICollection
IL_0025:  stloc.1
IL_0026:  ldloc.1
IL_0027:  brfalse.s  IL_0030
IL_0029:  ldloc.1
IL_002a:  callvirt   instance int32 [mscorlib]System.Collections.ICollection::get_Count()
IL_002f:  ret
IL_0030:  ldc.i4.0
IL_0031:  stloc.2
IL_0032:  ldarg.0
IL_0033:  callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource>::GetEnumerator()
IL_0038:  stloc.3
.try
{
IL_0039:  br.s       IL_003f
IL_003b:  ldloc.2
IL_003c:  ldc.i4.1
IL_003d:  add.ovf
IL_003e:  stloc.2
IL_003f:  ldloc.3
IL_0040:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_0045:  brtrue.s   IL_003b
IL_0047:  leave.s    IL_0053
}  // end .try
finally
{
IL_0049:  ldloc.3
IL_004a:  brfalse.s  IL_0052
IL_004c:  ldloc.3
IL_004d:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
IL_0052:  endfinally
}  // end handler
IL_0053:  ldloc.2
IL_0054:  ret
} // end of method Enumerable::Count

我使用IEnum<string>.ToArray<string>().Length,它工作正常。

你可以使用System.Linq。

using System;
using System.Collections.Generic;
using System.Linq;


public class Test
{
private IEnumerable<string> Tables
{
get {
yield return "Foo";
yield return "Bar";
}
}


static void Main()
{
var x = new Test();
Console.WriteLine(x.Tables.Count());
}
}

你会得到结果'2'。

我在一个方法中使用了这样的方法来检查传入的IEnumberable内容

if( iEnum.Cast<Object>().Count() > 0)
{


}

在这样的方法中:

GetDataTable(IEnumberable iEnum)
{
if (iEnum != null && iEnum.Cast<Object>().Count() > 0) //--- proceed further


}

它可能不会产生最好的性能,但你可以使用LINQ来计算IEnumerable中的元素:

public int GetEnumerableCount(IEnumerable Enumerable)
{
return (from object Item in Enumerable
select Item).Count();
}

我使用这样的代码,如果我有字符串列表:

((IList<string>)Table).Count

我发现最好的方法是把它转换成一个列表。

IEnumerable<T> enumList = ReturnFromSomeFunction();


int count = new List<T>(enumList).Count;

我认为这是最简单的方法

Enumerable.Count<TSource>(IEnumerable<TSource> source)

参考:system.linq.enumerable

.NET 6的LINQ中有一个新方法 看https://www.youtube.com/watch?v=sIXKpyhxHR8 < / p >
Tables.TryGetNonEnumeratedCount(out var count)

简化所有答案。

IEnumerable没有Count函数或属性。为了得到这个值,你可以存储计数变量(例如使用foreach),或者使用Linq来求解count。

如果你有:

IEnumerable<比;产品

然后:

声明:“using System.Linq;”

数:

.Count products.ToList ()