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()
或类似方法在内存中缓存结果,因为可能会有数以千计甚至数百万的结果(我有很多宠物)。到目前为止,我已经能够拼凑出这个笨拙的hack: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
- 它们都源自相同的通用数据,但在其他方面完全没有共同之处。