这会多次调用 GetEnumerator() 吗?

Will this call GetEnumerator() More than Once?

在我们的代码库中,我们有一个数据访问层,它提供的方法类似于:

public IEnumerable<Foo> GetFoos()
{
    var collection = new Collection<Foo>();

    // ...call db

    while (read results)
    {
        collection.Add(item);
    }

    return collection;
}

因此,虽然方法签名 return 是一个 IEnumerable,但在幕后,它创建了一个 Collection(已被枚举)。期望 IEnumerable 的此方法的使用者实际上会调用 GetEnumerator(),还是他们实际上会在不知不觉中使用 Collection?

IEnumerable<Foo> myFoos = GetFoos();

// will .Any() cause enumeration even though it's a Collection under the hood?
if (myFoos != null && myFoos.Any())
{
    // ...
}

是的,它会一直调用 GetEnumerator()

我们可以通过检查 the implementation of Any() on ReferenceSource.microsoft.com:

来验证这一点
public static bool Any<TSource>(this IEnumerable<TSource> source) {
    if (source == null) throw Error.ArgumentNull("source");
    using (IEnumerator<TSource> e = source.GetEnumerator()) {
        if (e.MoveNext()) return true;
    }
    return false;
}        

如果你关心性能,但又想限制消费者改变集合的能力,你可以考虑返回IReadOnlyCollection<T>。这实际上只是一个 IEnumerable<T> 和一个 Count 属性。然后消费者可以检查 Count 属性,但除了枚举它之外不能做任何事情。 (除非他们开始做一些令人讨厌的事情,比如将其转换为原始集合类型...)

如果您想在这种情况下避免对集合进行枚举,您可以执行以下操作:

ICollection col = myFoos as ICollection;
bool hasItems = false;
if (col != null && col.Count > 0)
{
   hasItems = true;
}
else
{
   hasItems = (myFoos != null && myFoos.Any());
}

但是这可能比仅仅调用 .Any() 有更多的开销——无论你从检查 .Count 中获得的任何节省都可能被检查 myFoos 是否为 [=] 的成本所抵消14=] 并加载它。这样做的唯一好处是,如果出于某些其他原因,您想像 ICollection 一样对对象进行操作。你的另一个选择是让你的 GetFoos 函数 return 和 ICollection 开始......