什么是首选的(高效且易读)IEnumerable<T>扩展方法链式调用方式?

6
如果我尝试在IEnumerable对象图的多个级别上过滤结果,那么链接扩展方法的首选方式是什么?
我可以使用任何扩展方法和Lambda表达式,但是我希望不使用LINQ语法,以保持与代码库的一致性。
将筛选推送到SelectMany()方法的选择器还是只链接另一个Where()方法更好?还是有更好的解决方案?
如何确定最佳选项?在这个测试用例中,所有内容都直接可用于内存。显然,下面的两个样本目前都产生了相同的正确结果;我只是在寻找一个理由,为什么喜欢其中之一(或另一种选择)。
public class Test
{
    // I want the first chapter of a book that's exactly 42 pages, written by
    // an author whose name is Adams, from a library in London.
    public Chapter TestingIEnumerableTExtensionMethods()
    {
        List<Library> libraries = GetLibraries();

        Chapter chapter = libraries
            .Where(lib => lib.City == "London")
            .SelectMany(lib => lib.Books)
            .Where(b => b.Author == "Adams")
            .SelectMany(b => b.Chapters)
            .First(c => c.NumberOfPages == 42);

        Chapter chapter2 = libraries
            .Where(lib => lib.City == "London")
            .SelectMany(lib => lib.Books.Where(b => b.Author == "Adams"))
            .SelectMany(b => b.Chapters.Where(c => c.NumberOfPages == 42))
            .First();
    }

这里是样例对象图:

public class Library
{
    public string Name { get; set; }
    public string City { get; set; }
    public List<Book> Books { get; set; }
}

public class Book
{
    public string Name { get; set; }
    public string Author { get; set; }
    public List<Chapter> Chapters { get; set; }
}

public class Chapter
{
    public string Name { get; set; }
    public int NumberOfPages { get; set; }
}

1
可读性似乎差不多。性能取决于这是针对对象还是其他内容的linq; 为什么不测量一下呢? - phoog
你的查询是否总是以“库=>书籍=>章节”的形式,或者可能有更复杂的关系(我猜这只是一个测试模型)。 - NSGaga-mostly-inactive
@NSGaga 他们总是以层次形式存在,但你说得对,这只是一个测试模型,使读者更容易理解。从技术上讲,我正在使用EDI(ANSI X12)结构,因此涉及文件、ISA、GS和ST段。 - BQ.
@phoog 我同意测量是个好主意,但在这里的示例和我正在处理的实际代码中,我没有任何迹象表明我的数据可能会有多大的变化。在某些情况下,我将有1个库,每个库有3本书,每本书有5000章节。下一次我可能有1个库,每个库有5000本书,每本书只有1章节。因为我无法准确预测其中之一,所以测量并不理想,我希望得到一般性的指导,并让可读性成为更核心的目标。 - BQ.
我认为使用LINQ语法会使这样的代码更易读。而且我认为牺牲一些一致性是值得的。 - svick
4个回答

3
根据您使用的LINQ实现方式,最好的方法可能不同。LinqToSql与内存过滤的表现不同。子句的顺序应取决于所使用的数据,因为天真的实现会在序列早期过滤更多的记录,这意味着后续方法的工作量更小。
对于您的两个示例,我会猜测性能差异可以忽略不计,而且会偏向于第一种,因为它允许更容易地独立修改每个子句。
至于确定最佳选项,与其他任何事情一样:进行测试。

2

我猜第一个表达式会稍微快一点,但并不明显。要真正确定哪个更快,您需要使用分析器或计时器对它们进行测量。

无论哪种方式,可读性似乎都没有受到强烈影响。我更喜欢第一种方法,因为它的嵌套级别较少。这完全取决于您个人的喜好。


是的,第一个会更快,因为您不会为每个项目创建 Where 迭代器。 - usr

1

这取决于底层的LINQ提供程序如何工作。对于LINQ to Objects,在这种情况下,两者所需的工作量大致相同。但那是最简单的例子,因此除此之外很难说。


0

这可能会给你不同的角度,尽管这更多地是风格问题...
我有时会做类似于这样的事情...

return libraries.Filter(
        l => l.City == "",
        l => l.Books,
        b => b.Author == "Adams",
        b => b.Chapters,
        c => c.NumberOfPages == 42
        );

...在这里你可以猜测扩展名是什么,类似于...

public static IEnumerable<TC> Filter<TL, TB, TC>(this IEnumerable<TL> list,
    Func<TL, bool> whereLs,
    Func<TL, IEnumerable<TB>> selectBs,
    Func<TB, bool> whereBs,
    Func<TB, IEnumerable<TC>> selectCs,
    Func<TC, bool> whereCs
    )
{
    return list
        .Where(whereLs)
        .SelectMany(selectBs)
        .Where(whereBs)
        .SelectMany(selectCs)
        .Where(whereCs);
}

...或者...

...    
{
    return list
        .Where(whereLs)
        .SelectMany(l => selectBs(l).Where(whereBs))
        .SelectMany(b => selectCs(b).Where(whereCs));
}

而且组合/选项很多,取决于你拥有什么,你如何“喜欢编码”(将其抽象化或者“捕获”,“参数化”例如PerCityAuthorPages(_city, _author, _numPages);等)

基本上,我不喜欢有这么多的“Where”,“Select”等等,对我来说可读性不高。 使用“简短形式”,很清楚哪个是哪个,where、select等等都很明确,而且非常简洁,字符更少。

此外,你可以推迟对Where/Select组合的决定(根据需要和提供者选择其中之一)

@Telastyn的观点是正确的,LINQ提供程序,例如如果您查看一些实现代码, 通过表达式缩减等方式 在某种程度上具有相当大的非确定性(即从一个提供程序到另一个提供程序),它们可能最终映射到例如SQL, 虽然我认为大多数情况下应该是相同的。


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