在不进行迭代的情况下,如何计算 IEnumerable 中的项数?

19 浏览
0 Comments

在不进行迭代的情况下,如何计算 IEnumerable 中的项数?

假设我想要遍历这些表,并且想要写出类似于“正在处理第n个表,共m个表”的内容。有没有一种方法可以在主循环之前找出m的值,而不需要进行先前的迭代?

希望我表达清楚了。

0
0 Comments

问题的出现原因:

问题的出现是因为在对一个`IEnumerable`进行计数时,需要遍历整个集合来得到元素的数量。然而,有时我们并不想遍历整个集合,而是希望能够以更高效的方式获取集合的元素数量。

解决方法:

有几种解决方法可以避免在迭代时计数集合中的元素:

1. 使用`Count()`扩展方法:`Count()`扩展方法并不总是进行迭代。例如,在Linq to Sql中,计数是通过向数据库发出`Count()`命令并返回结果来实现的,而不是将所有行都带回来。此外,编译器(或运行时)足够聪明,如果对象有`Count()`方法,它会直接调用该方法。因此,`Count()`方法并不是像其他回答者所说的完全无知地进行迭代来计数元素。

2. 使用`Any()`扩展方法:在许多情况下,如果程序员只是想检查`IEnumerable`是否为空,使用`Any()`扩展方法比使用`Count()`扩展方法更高效。`Any()`方法具有Linq的延迟求值特性,一旦确定存在任何元素,就可以终止遍历。而且,使用`Any()`方法的代码更易读。

3. 对于集合和数组,可以直接使用`Count`属性或`Array.Length`属性来获取元素的数量。对于集合来说,`Count`属性总是知道集合的大小,查询`collection.Count`不需要任何额外的计算,只需返回已知的计数即可。对于数组来说,`Array.Length`属性也是同样的道理。对于枚举器,可以使用`Any()`方法来判断是否有元素存在。

需要注意的是,第一个观点并不完全正确。LINQ to SQL使用的是不同于上述提到的两个扩展方法。如果使用第二个扩展方法,计数将在内存中执行,而不是作为SQL函数执行。另外,如果将`IQueryable`显式转换为`IEnumerable`,它将不会发出SQL计数命令。在写这篇回答时,Linq to Sql是新的技术,我认为我只是在推动使用`Any()`方法,因为对于枚举器、集合和SQL来说,它更高效且更易读。感谢您对答案的改进。

0
0 Comments

在使用C#编程时,有时候我们需要统计一个IEnumerable中的元素数量,但是IEnumerable本身并不支持直接获取元素数量的功能。这是因为IEnumerable使用惰性求值的方式,在需要元素时才会获取它们。

如果我们想要在不遍历元素的情况下知道元素的数量,可以使用ICollection接口,它具有一个Count属性。

如果不需要通过索引访问列表,我更倾向于使用ICollection而不是IList。

通常情况下,我习惯于使用List和IList。但是特别是在需要自己实现列表时,使用ICollection更简单,而且也有Count属性。

那么当我们有一个给定的IEnumerable时,如何检查其元素的数量呢?你可以遍历并计数元素,或者调用Linq命名空间中的Count()方法来实现。

在正常情况下,简单地将IEnumerable替换为IList是否足够呢?由于IEnumerable的惰性求值特性,它可以用于IList无法使用的场景。比如,你可以构建一个返回IEnumerable的函数,用于枚举圆周率的所有小数位数。只要你不尝试完整地foreach整个结果,它应该可以正常工作。而你无法创建一个包含圆周率的IList。但是这些都是理论上的问题,对于大多数正常的使用场景,我完全同意。如果你需要获取元素数量,你需要IList。

明白了,谢谢。我将我的IEnumerable转换为了IList,没有任何问题!

那么如果你不知道传入的列表的类型(字典、列表等),你想要你的方法更灵活,允许调用者指定类型。但是你仍然需要遍历这个传入的列表或字典怎么办?我猜你可以在方法内部检查传入的类型,然后执行相应的操作。

如果你需要一个字典怎么办?

没有使用IEnumerable是没有问题的,我通常在方法签名中使用这个类型,以增加灵活性,就像你说的那样。但是如果你真的需要在不遍历元素的情况下获取集合的元素数量,那么使用IList(或者正如Michael Meadows所建议的更好的ICollection)是正确的方法。如果我没有记错的话,IDictionary也是从IList继承而来的,作为键值对的列表。

非泛型的ICollection的一个优点是,它可以在不知道泛型集合的实际元素类型的情况下使用。例如,假设一个接受IEnumerable的方法得到了一个DogHouse的实例,它实现了IList和ICollection。检查一个IEnumerable是否实现了ICollection要比找到它实现的泛型IList要容易得多。

要处理不同类型的集合并且高效地编写方法,有时候需要编写多个版本的方法。这违反了DRY原则,但在C#中,如果count对性能有帮助,这是不可避免的权衡考虑。如果要处理的集合是List,则可以使用abc(ICollection c) { abc(c, c.Count); }和abc(IDictionary d) { abc(d.Values, d.Count); }这两种方法来处理。

通过使用ICollection或IList接口,我们可以在不遍历元素的情况下获取IEnumerable的元素数量。这对于编写灵活且高效的代码非常有用。

0
0 Comments

在使用IEnumerable的Count方法时,它首先尝试将其转换为ICollection,如果可能的话,使用ICollection的Count属性。否则,它会进行迭代来计数。因此,最好使用Count()扩展方法来获得最佳的性能。

这种实现方式很有趣,大多数Enumerable中的扩展方法都对各种类型的序列进行了优化,如果可能的话,它们会使用更便宜的方法。

在.NET 3.5中引入了这个扩展方法,并在MSDN中有文档记录。

有人认为在IEnumerator enumerator = source.GetEnumerator()中不需要泛型类型T,可以直接使用IEnumerator enumerator = source.GetEnumerator()。但实际上,IEnumerable继承了IDisposable接口,而IEnumerable没有。因此,无论如何调用GetEnumerator方法,都应该使用var d = e as IDisposable; if (d != null) d.Dispose();来释放资源。

另外,这也取决于你对结果的处理。通常情况下,你希望类型是明确的。否则,在使用GetEnumerator返回的object时,你需要将它们转换为预期的类型。如果出现错误,比如传递了一个元素类型与预期不同的集合,会导致运行时异常而不是编译时错误。

遗憾的是,.NET没有介于IEnumerable和ICollection之间的中间接口,用于具有已知数量的IEnumerable(但不需要ICollection的其他特性)。也没有办法知道,对于任意的IEnumerable和在算法中需要提前知道“Count”的情况,调用IEnumerable.Count会迭代两次的性能是否更好,还是先收集到一个列表中,然后使用列表的Count。更糟糕的是,迭代可能会有“副作用”。另一方面,枚举可能有很多元素。

另一种语法是(enumerator as IDisposable)?.Dispose()。

需要注意的是,实现IDisposable接口的是IEnumerator,而不是IEnumerable

所以,结论是"所以你最好使用Count()扩展方法来获得最佳的性能",只有当你使用的类型实现了ICollection时,这个结论才是正确的。如果类型只实现了IEnumerable,将会进行完全迭代。从这个答案中可以得到的一个重要教训是,在自定义类型上实现IEnumerable时,不仅仅要停留在那里。还要实现IReadOnlyCollection,然后实现Count {get;},这样当用户访问你的类型时,他们将获得最佳的性能。

0