我对枚举器和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的类允许您使用foreach语法。

基本上,它有一个获取集合中下一个项的方法。它不需要将整个集合存储在内存中,也不知道其中有多少项,foreach只需要不断获取下一项,直到用完为止。

这在某些情况下非常有用,例如在大型数据库表中,您不希望在开始处理行之前将整个内容复制到内存中。

现在List实现IEnumerable,但表示内存中的整个集合。如果您有一个IEnumerable,并调用.ToList(),则会在内存中创建一个包含枚举内容的新列表。

linq表达式返回一个枚举,默认情况下,当您使用foreach遍历时,该表达式将执行。迭代foreach时执行IEnumerable linq语句,但可以使用.ToList()强制其更快地迭代。

我的意思是:

var things = 
    from item in BigDatabaseCall()
    where ....
    select item;

// this will iterate through the entire linq statement:
int count = things.Count();

// this will stop after iterating the first one, but will execute the linq again
bool hasAnyRecs = things.Any();

// this will execute the linq statement *again*
foreach( var thing in things ) ...

// this will copy the results to a list in memory
var list = things.ToList()

// this won't iterate through again, the list knows how many items are in it
int count2 = list.Count();

// this won't execute the linq statement - we have it copied to the list
foreach( var thing in list ) ...

其他回答

实现IEnumerable的类允许您使用foreach语法。

基本上,它有一个获取集合中下一个项的方法。它不需要将整个集合存储在内存中,也不知道其中有多少项,foreach只需要不断获取下一项,直到用完为止。

这在某些情况下非常有用,例如在大型数据库表中,您不希望在开始处理行之前将整个内容复制到内存中。

现在List实现IEnumerable,但表示内存中的整个集合。如果您有一个IEnumerable,并调用.ToList(),则会在内存中创建一个包含枚举内容的新列表。

linq表达式返回一个枚举,默认情况下,当您使用foreach遍历时,该表达式将执行。迭代foreach时执行IEnumerable linq语句,但可以使用.ToList()强制其更快地迭代。

我的意思是:

var things = 
    from item in BigDatabaseCall()
    where ....
    select item;

// this will iterate through the entire linq statement:
int count = things.Count();

// this will stop after iterating the first one, but will execute the linq again
bool hasAnyRecs = things.Any();

// this will execute the linq statement *again*
foreach( var thing in things ) ...

// this will copy the results to a list in memory
var list = things.ToList()

// this won't iterate through again, the list knows how many items are in it
int count2 = list.Count();

// this won't execute the linq statement - we have it copied to the list
foreach( var thing in list ) ...

我将分享一个被误用的概念:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));


// updating existing list
names[0] = "ford";

// Guess what should be printed before continuing
print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

预期结果

// I was expecting    
print( startingWith_M.ToList() ); // mercedes, mazda
print( startingWith_F.ToList() ); // fiat, ferrari

实际结果

// what printed actualy   
print( startingWith_M.ToList() ); // mazda
print( startingWith_F.ToList() ); // ford, fiat, ferrari

解释

根据其他答案,结果的评估被推迟到调用ToList或类似的调用方法(例如ToArray)。

因此,我可以将这种情况下的代码重写为:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

// updating existing list
names[0] = "ford";

// before calling ToList directly
var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));

print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

在附近玩耍

https://repl.it/E8Ki/0

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

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

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

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

IEnumerable描述行为,而List是该行为的实现。当您使用IEnumerable时,您给编译器一个机会,将工作推迟到以后,可能会在这一过程中进行优化。如果使用ToList(),则强制编译器立即将结果具体化。

每当我“堆叠”LINQ表达式时,我都会使用IEnumerable,因为通过只指定行为,我给了LINQ一个延迟求值并可能优化程序的机会。还记得LINQ如何在枚举数据库之前不生成SQL来查询数据库吗?考虑一下:

public IEnumerable<Animals> AllSpotted()
{
    return from a in Zoo.Animals
           where a.coat.HasSpots == true
           select a;
}

public IEnumerable<Animals> Feline(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Felidae"
           select a;
}

public IEnumerable<Animals> Canine(IEnumerable<Animals> sample)
{
    return from a in sample
           where a.race.Family == "Canidae"
           select a;
}

现在您有了一个选择初始样本(“AllSpoted”)的方法,以及一些过滤器。现在你可以这样做了:

var Leopards = Feline(AllSpotted());
var Hyenas = Canine(AllSpotted());

那么,使用List比IEnumerable更快吗?仅当您希望防止查询被多次执行时。但总体而言,情况是否更好?在上面的例子中,Leopards和Hyenas分别被转换为单个SQL查询,数据库只返回相关的行。但是,如果我们从AllSpotted()返回了一个List,那么它可能会运行得更慢,因为数据库可能会返回比实际需要的数据多得多的数据,而且我们在客户端进行过滤会浪费时间。

在程序中,最好将查询转换为列表,直到最后,所以如果我要多次枚举Leopards和Hyenas,我会这样做:

List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();