我对枚举器和LINQ的工作方式有些怀疑。考虑以下两个简单的选择:

List<Animal> sel = (from animal in Animals 
                    join race in Species
                    on animal.SpeciesKey equals race.SpeciesKey
                    select animal).Distinct().ToList();

or

IEnumerable<Animal> sel = (from animal in Animals 
                           join race in Species
                           on animal.SpeciesKey equals race.SpeciesKey
                           select animal).Distinct();

我更改了原始对象的名称,使其看起来像一个更通用的示例。查询本身并不那么重要。我想问的是:

foreach (Animal animal in sel) { /*do stuff*/ }

我注意到,如果我使用IEnumerable,当我调试和检查“sel”(在这种情况下是IEnumeraable)时,它有一些有趣的成员:“internal”、“outer”、“innerKeySelector”和“outerKeySelecter”,最后两个似乎是委托。“内部”成员中没有“动物”实例,而是“物种”实例,这对我来说很奇怪。我想这两位代表会决定哪一个进入,哪一个退出?我注意到,如果我使用“Distinct”,“inner”包含6项(这是不正确的,因为只有2项是Distinct),但“outer”包含正确的值。同样,可能是委托方法决定了这一点,但这比我对IEnumerable的了解要多得多。最重要的是,这两个选项中哪一个性能最佳?

通过.ToList()进行的邪恶列表转换?

或者直接使用枚举器?

如果可以的话,也请解释一下或提供一些链接来解释IEnumerable的用法。


当前回答

如果您只想枚举它们,请使用IEnumerable。

不过,请注意,更改正在枚举的原始集合是一个危险的操作——在这种情况下,您将需要首先ToList。这将为内存中的每个元素创建一个新的列表元素,枚举IEnumerable,因此如果只枚举一次,性能会降低-但更安全,有时list方法很方便(例如在随机访问中)。

其他回答

除了上面贴出的所有答案,这是我的两分钱。除List之外,还有许多其他类型实现IEnumerable,如ICollection、ArrayList等。因此,如果我们将IEnumeraable作为任何方法的参数,我们可以将任何集合类型传递给函数。也就是说,我们可以有方法来操作抽象,而不是任何特定的实现。

这里有一篇很好的文章:Claudio Bernasconi的TechBlog:何时使用IEnumerable、ICollection、IList和List

以下是一些关于场景和功能的基本要点:

如果您只想枚举它们,请使用IEnumerable。

不过,请注意,更改正在枚举的原始集合是一个危险的操作——在这种情况下,您将需要首先ToList。这将为内存中的每个元素创建一个新的列表元素,枚举IEnumerable,因此如果只枚举一次,性能会降低-但更安全,有时list方法很方便(例如在随机访问中)。

在许多情况下(例如无限列表或非常大的列表),IEnumerable无法转换为list。最明显的例子是所有的质数,facebook的所有用户及其详细信息,或者ebay上的所有项目。

不同之处在于,“List”对象“就在此时此地”存储,而“IEnumerable”对象“一次只能存储一个”。所以,如果我在ebay上浏览所有项目,一次一个,即使是一台小型计算机也能处理,但“.ToList()”肯定会耗尽我的内存,无论我的计算机有多大。没有一台计算机能够单独包含和处理如此庞大的数据量。

[编辑]-不用说-这不是“这个或那个”。通常,在同一类中同时使用列表和IEnumerable是很有意义的。世界上没有一台计算机能列出所有素数,因为根据定义,这需要无限的内存。但是您可以很容易地想到一个类PrimeContainer,它包含IEnumerable<long>素数,由于明显的原因,它还包含SortedList<long<_primes。迄今为止计算的所有素数。要检查的下一个素数将只针对现有素数运行(直到平方根)。通过这种方式,您可以同时获得一个素数(IEnumerable)和一个很好的“迄今为止的素数”列表,这是整个(无限)列表的很好的近似值。

要认识到的最重要的一点是,使用Linq时,查询不会立即得到求值。它只是作为在foreach中迭代生成的IEnumerable<T>的一部分运行的,这是所有奇怪的代理都在做的。

因此,第一个示例通过调用ToList并将查询结果放在列表中来立即评估查询。第二个示例返回一个IEnumerable<T>,其中包含稍后运行查询所需的所有信息。

就性能而言,答案是这取决于。如果您需要立即评估结果(例如,您正在改变稍后查询的结构,或者如果您不希望IEnumerable<t>上的迭代需要很长时间),请使用列表。否则使用IEnumerable<T>。默认情况下,在第二个示例中应使用按需评估,因为这通常使用较少的内存,除非有特定原因将结果存储在列表中。