IEnumerable泛型的扩展方法

5

我有两个IEnumerable的扩展:

public static class IEnumerableGenericExtensions
{
    public static IEnumerable<IEnumerable<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
    {
        List<T> toReturn = new List<T>(max);
        foreach (var item in source)
        {
            toReturn.Add(item);
            if (toReturn.Count == max)
            {
                yield return toReturn;
                toReturn = new List<T>(max);
            }
        }
        if (toReturn.Any())
        {
            yield return toReturn;
        }
    }

    public static int IndexOf<T>(this IEnumerable<T> source, Predicate<T> searchPredicate)
    {
        int i = 0;
        foreach (var item in source)
            if (searchPredicate(item))
                return i;
            else
                i++;

        return -1;
    }
}

我写下了以下的代码:

        Pages = history.InSetsOf<Message>(500);
        var index = Pages.IndexOf(x => x == Pages.ElementAt(0));

在以下代码中:

public class History : IEnumerable

我期望得到的结果是'0',但是实际上返回的值是'-1'。我不明白为什么会这样。

2个回答

5
当您编写 Pages.IndexOf(x => x == Pages.ElementAt(0)); 时,实际上由于 deferred execution(也称为lazy),会运行 InSetsOf 多次。具体如下:
  • Pages = history.InSetsOf<Message>(500) - 这一行根本不执行 InSetsOf
  • Pages.IndexOf - 遍历 Pages,因此它开始执行 InSetsOf 一次。
  • x == Pages.ElementAt(0) - 这会再次执行 InSetsOf,对于Pages集合中的每个元素都执行一次(或者至少直到searchPredicate返回true,在这里不会发生)。
每次运行InSetsOf,您都会创建一个新列表(具体而言,是一个新的第一个列表,因为您使用ElementAt(0))。这些是两个不同的对象,因此它们之间的==比较失败。
一个非常简单的解决方法是返回一个列表,以便Pages不是延迟查询,而是一个具体的集合:
Pages = history.InSetsOf<Message>(500).ToList();

另一个选择是使用SequenceEqual,不过我建议无论如何都要缓存第一个元素:
Pages = history.InSetsOf<Message>(500);
var firstPage = Pages.FirstOrDefault();
var index = Pages.IndexOf(x => x.SequenceEqual(firstPage));

非常感谢!我完全忘记了 LINQ 的这个特别之处。 - Seekeer
@Seekeer - 没问题,很高兴能帮助!这是一个棘手的问题-很容易犯这样的错误。最糟糕的部分是当你没有看到一个错误时,但是代码似乎运行良好,而实际上非常缓慢。(除了错误之外,在这里使用或不使用 ToList 有很大的复杂度差异) - Kobi
是的,我开始理解那些不喜欢LINQ的人了。顺便问一下,你能推荐一些关于LINQ查询效率和优化的好文章或书籍吗? - Seekeer
1
@Seekeer - 我知道一个。Jon Skeet 写了一系列文章,重新实现了 Linq to Objects(http://msmvps.com/blogs/jon_skeet/archive/2011/02/23/reimplementing-linq-to-objects-part-45-conclusion-and-list-of-posts.aspx),我觉得非常有趣。至于 Linq:大多数抱怨都是针对 Linq-to-Sql 太慢。在我看来,Linq-to-Objects 可以节省很多代码,值得 "喜欢"。Linq 是一种工具,你应该知道什么时候它会帮助你,什么时候它会妨碍你。 - Kobi
感谢提供的信息。"你应该知道工具何时帮助你,何时阻碍你",我认为这句话应该成为所有开发人员的座右铭。 - Seekeer

1
你的类T是否实现了IComparable接口?如果没有,你的相等性检查可能存在缺陷,因为框架无法确定何时T = T。我猜你只需在类T上重写equals方法就可以解决这个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接