将延迟的IEnumerable <T>拆分为两个序列而不进行重新评估?

Aar*_*ght 9 .net c# linq ienumerable iterator

我有一个方法需要处理传入的命令序列,并根据结果的某些属性将结果拆分到不同的桶中.例如:

class Pets
{
    public IEnumerable<Cat> Cats { get; set; }
    public IEnumerable<Dog> Dogs { get; set; }
}

Pets GetPets(IEnumerable<PetRequest> requests) { ... }
Run Code Online (Sandbox Code Playgroud)

底层模型完全能够PetRequest一次处理整个元素序列,而且PetRequest大部分都是ID等通用信息,因此尝试在输入处拆分请求是没有意义的.但是提供者实际上并没有回馈CatDog实例,只是一个通用的数据结构:

class PetProvider
{
    IEnumerable<PetData> GetPets(IEnumerable<PetRequest> requests)
    {
        return HandleAllRequests(requests);
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经命名了响应类型,PetData而不是Pet清楚地表明它不是超类CatDog- 换句话说,转换为Cat或是Dog映射过程.另一件事要记住的是,HandleAllRequests价格昂贵,如数据库查询,所以我真的不想再说一遍,而且我宁愿避免使用缓存在内存中的结果ToArray()或类似的,因为可能有几千数百万的结果(我有很多宠物).

到目前为止,我已经能够把这个笨拙的黑客扔到一起:

Pets GetPets(IEnumerable<PetRequest> requests)
{
    var data = petProvider.GetPets(requests);
    var dataGroups = 
        from d in data
        group d by d.Sound into g
        select new { Sound = g.Key, PetData = g };
    IEnumerable<Cat> cats = null;
    IEnumerable<Dog> dogs = null;
    foreach (var g in dataGroups)
        if (g.Sound == "Bark")
            dogs = g.PetData.Select(d => ConvertDog(d));
        else if (g.Sound == "Meow")
            cats = g.PetData.Select(d => ConvertCat(d));
    return new Pets { Cats = cats, Dogs = dogs };
}
Run Code Online (Sandbox Code Playgroud)

这在技术上是有效的,因为它不会导致PetData结果被枚举两次,但它有两个主要问题:

  1. 它在代码上看起来像一个巨大的疙瘩; 它有点像我们在LINQ前2.0版框架中必须使用的糟糕的命令式样式.

  2. 它最终是一个彻头彻尾的毫无意义的练习,因为这个GroupBy方法只是将所有这些结果缓存在内存中,这意味着我真的没有比我只是懒惰并且ToList()在第一时间完成一个并附加一些谓词的情况更好..

所以重申一下这个问题:

是否可以将单个延迟IEnumerable<T>实例拆分为两个IEnumerable<?>实例,而不执行任何急切评估,将结果缓存到内存中,或者必须再次重新评估原始实例IEnumerable<T>

基本上,这将是一个Concat操作的反向..NET框架中还没有一个这样的事实强烈表明这甚至不可能,但我认为无论如何都不会有任何问题.

PS请不要告诉我创建一个Pet超类并返回一个IEnumerable<Pet>.我曾经CatDog有趣的例子,但在现实的结果类型更像是ItemError-它们都来自同一个通用数据衍生而来,但否则没有什么共同之处.

Jon*_*eet 12

从根本上说,没有.试想一下,如果它可能的.然后考虑如果我这样做会发生什么:

foreach (Cat cat in pets.Cats)
{
    ...
}

foreach (Dog dog in pets.Dogs)
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

这需要首先处理所有的猫,然后是所有的狗...所以如果第一个元素是一个原始序列会发生什么Dog?它要么必须缓存它,要么跳过它 - 它不能返回它,因为我们仍然要求它Cats.

可以实现一些只需要缓存的东西,但这可能是整个一个序列,因为典型的用法是完全评估一个序列或另一个序列.

如果可能的话,你真的只想在你拿走它们时处理宠物(无论是猫还是狗).难道是可行的提供Action<Cat>Action<Pet>和执行正确的处理程序为每个项目?

  • @Aaronaught:啊 - 如果所有的狗都必须来到所有的猫之前,那你就别无选择* - 必须在某个地方进行一些缓存.现在这可能是序列化的形式 - 将狗序列化为"真实"流,将猫序列化为MemoryStream,然后在到达宠物末尾时将MemoryStream复制到真实流中.从根本上说,如果你先读一只猫,你可以考虑一下你可以做什么 - 你要么处理它,要么忘掉它,要么缓存它.没有任何其他选择:) (5认同)
  • 经过进一步的反思,我认为"根据需要缓存"策略实际上可能适用于这种情况.在大多数情况下,*大多数*结果将在第一个桶("猫")中,所以我可以尝试使用一个系统来查找"特殊"结果("狗")并迭代"正常"结果. (2认同)