拆分延迟的IEnumerable<;T>;分为两个序列而无需重新评估

本文关键字:评估 两个 新评估 lt IEnumerable 延迟 gt 拆分 | 更新日期: 2023-09-27 17:59:12

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

class Pets
{
    public IEnumerable<Cat> Cats { get; set; }
    public IEnumerable<Dog> Dogs { get; set; }
}
Pets GetPets(IEnumerable<PetRequest> requests) { ... }

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

class PetProvider
{
    IEnumerable<PetData> GetPets(IEnumerable<PetRequest> requests)
    {
        return HandleAllRequests(requests);
    }
}

我将响应类型命名为PetData而不是Pet,以清楚地表明它是而不是CatDog的超类——换句话说,转换为CatDog是一个映射过程。另一件需要记住的事情是,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 };
}

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

  1. 它看起来像代码上的一个巨大的丘疹;它有点像我们在LINQ框架2.0之前一直必须使用的糟糕的命令式风格。

  2. 这最终是一个完全没有意义的练习,因为GroupBy方法只是将所有这些结果缓存在内存中,这意味着我真的不会比懒惰一开始做了ToList()并附加了一些谓词更好。

因此,重申这个问题:

是否可以将一个延迟的IEnumerable<T>实例拆分为两个IEnumerable<?>实例,而无需执行任何急切的评估、将结果缓存在内存中,或重新评估原始IEnumerable<T>

基本上,这将与Concat操作相反。事实上,.NET框架中还没有一个,这有力地表明这可能是不可能的,但我认为无论如何问也无妨。

p.S.请不要告诉我创建Pet超类,只返回IEnumerable<Pet>。我用CatDog作为有趣的例子,但实际上结果类型更像ItemError——它们都是从相同的通用数据派生的,但在其他方面根本没有共同点

拆分延迟的IEnumerable<;T>;分为两个序列而无需重新评估

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

foreach (Cat cat in pets.Cats)
{
    ...
}
foreach (Dog dog in pets.Dogs)
{
    ...
}

这需要先处理所有的猫,然后处理所有的狗。。。那么,如果第一个元素是Dog,原始序列会发生什么呢?它要么必须缓存它,要么跳过它——它无法返回它,因为我们仍在请求Cats

可以实现只缓存所需数量的东西,但很可能是一个序列的整体,因为典型的用法是完全评估一个或另一个序列。

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

乔恩说了什么(我相信我是第一百万个这么说的人)。

我可能只是去老派做:

List<Cat> cats = new List<Cat>();
List<Dog> dog = new List<Dog>();
foreach(var pet in data)
{
   if (g.Sound == "Bark")
     dogs.Add(ConvertDog(pet));
   else if (pet.Sound == "Meow")
     cats.Add(ConvertCat(pet));
}

但我意识到这并不完全是你想做的-但后来你确实说了re-评估-这只评估了一次:)