拆分延迟的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一样的通用信息,因此尝试在输入处拆分请求是没有意义的。但是提供者实际上并没有返回Cat
和Dog
实例,只是一个通用的数据结构:
class PetProvider
{
IEnumerable<PetData> GetPets(IEnumerable<PetRequest> requests)
{
return HandleAllRequests(requests);
}
}
我将响应类型命名为PetData
而不是Pet
,以清楚地表明它是而不是Cat
或Dog
的超类——换句话说,转换为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 };
}
从技术上讲,这是可行的,因为它不会导致PetData
结果被枚举两次,但它有两个主要问题:
它看起来像代码上的一个巨大的丘疹;它有点像我们在LINQ框架2.0之前一直必须使用的糟糕的命令式风格。
这最终是一个完全没有意义的练习,因为
GroupBy
方法只是将所有这些结果缓存在内存中,这意味着我真的不会比懒惰一开始做了ToList()
并附加了一些谓词更好。
因此,重申这个问题:
是否可以将一个延迟的IEnumerable<T>
实例拆分为两个IEnumerable<?>
实例,而无需执行任何急切的评估、将结果缓存在内存中,或重新评估原始IEnumerable<T>
基本上,这将与Concat
操作相反。事实上,.NET框架中还没有一个,这有力地表明这可能是不可能的,但我认为无论如何问也无妨。
p.S.请不要告诉我创建Pet
超类,只返回IEnumerable<Pet>
。我用Cat
和Dog
作为有趣的例子,但实际上结果类型更像Item
和Error
——它们都是从相同的通用数据派生的,但在其他方面根本没有共同点
从根本上说,没有。想象一下,如果是可能的。然后考虑如果我这样做会发生什么:
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-评估-这只评估了一次:)