在C#中动态解压缩IEnumerable或最佳替代方案

让我们假设您有一个函数返回一个延迟枚举的对象:

struct AnimalCount { int Chickens; int Goats; } IEnumerable FarmsInEachPen() { .... yield new AnimalCount(x, y); .... } 

您还有两个使用两个独立IEnumerable的函数,例如:

 ConsumeChicken(IEnumerable); ConsumeGoat(IEnumerable); 

如何在没有a)事先转换FarmsInEachPen() ToList()的情况下调用ConsumeChickenConsumeGoat ,因为它可能有两个zillion记录,b)没有multithreading。

基本上:

 ConsumeChicken(FarmsInEachPen().Select(x => x.Chickens)); ConsumeGoats(FarmsInEachPen().Select(x => x.Goats)); 

但是没有强制双重枚举。

我可以使用multithreading来解决它,但是每个列表的缓冲队列都会变得不必要地复杂化。

所以我正在寻找一种方法将AnimalCount枚举器分成两个int枚举器而不完全评估AnimalCount 。 在锁步中一起运行ConsumeGoatConsumeChicken没有问题。

我能够完全理解解决方案,但我并不在那里。 我正在考虑一个辅助函数,它返回一个IEnumerable被输入到ConsumeChicken ,每次使用迭代器时,它在内部调用ConsumeGoat ,从而在锁步中执行这两个函数。 当然,除了我不想多次打电话给ConsumeGoat ..

我不认为有办法做你想要的,因为ConsumeChickens(IEnumerable)ConsumeGoats(IEnumerable)被顺序调用,每个人都单独枚举一个列表 – 你怎么期望工作没有两个单独的列表枚举?

根据具体情况,更好的解决方案是使用ConsumeChicken(int)ConsumeGoat(int)方法(每个方法都使用一个项目) ,并交替调用它们。 像这样:

 foreach(var animal in animals) { ConsomeChicken(animal.Chickens); ConsomeGoat(animal.Goats); } 

这将只列举animals集合一次。


另外,注意:取决于您的LINQ提供商以及您正在尝试做什么,可能有更好的选择。 例如,如果您尝试使用linq-to-sql或linq-to-entities从数据库获取鸡和山羊的总和,则以下查询..

 from a in animals group a by 0 into g select new { TotalChickens = g.Sum(x => x.Chickens), TotalGoats = g.Sum(x => x.Goats) } 

将导致单个查询,并在数据库端进行求和,这比将整个表拉过并在客户端进行求和更为可取。

你提出问题的方式,没有办法做到这一点。 IEnumerable是一个可枚举 – 也就是说,你可以将GetEnumerator放到序列的前面,然后反复询问“给我下一个项目”( MoveNext / Current )。 在一个线程中,你不能从animals.Select(a => a.Chickens)那里得到两个不同的东西animals.Select(a => a.Chickens)animals.Select(a => a.Chickens) animals.Select(a => a.Goats)同时。 你必须做一个然后另一个(这需要实现第二个)。

BlueRaja提出的建议是一种稍微改变问题的方法。 我建议走那条路。

另一种方法是利用微软的反应式扩展(Rx)中的IObservable推送可枚举 。 我不会详细介绍你将如何做到这一点,但这是你可以研究的东西。

编辑:

以上假设ConsumeChickensConsumeGoats都返回void或至少不返回IEnumerable本身 – 这似乎是一个明显的假设。 如果蹩脚的下行者真的会发表评论,我会很感激。

实际上简单的方法来实现你将什么转换为FarmsInEachPen返回值来推送集合或IObservable并使用ReactiveExtensions来处理它

 var observable = new Subject() observable.Do(x=> DoSomethingWithChicken(x. Chickens)) observable.Do(x=> DoSomethingWithGoat(x.Goats)) foreach(var item in FarmsInEachPen()) { observable.OnNext(item) } 

我想通了,谢谢很大程度上归功于@Lee让我走的路。

您需要在两个拉链之间共享一个枚举器,并使用适配器函数将正确的元素投影到序列中。

 private static IEnumerable ConsumeChickens(IEnumerable xList) { foreach (var x in xList) { Console.WriteLine("X: " + x); yield return null; } } private static IEnumerable ConsumeGoats(IEnumerable yList) { foreach (var y in yList) { Console.WriteLine("Y: " + y); yield return null; } } private static IEnumerable SelectHelper(IEnumerator enumerator, int i) { bool c = i != 0 || enumerator.MoveNext(); while (c) { if (i == 0) { yield return enumerator.Current.Chickens; c = enumerator.MoveNext(); } else { yield return enumerator.Current.Goats; } } } private static void Main(string[] args) { var enumerator = GetAnimals().GetEnumerator(); var chickensList = ConsumeChickens(SelectHelper(enumerator, 0)); var goatsList = ConsumeGoats(SelectHelper(enumerator, 1)); var temp = chickensList.Zip(goatsList, (i, i1) => (object) null); temp.ToList(); Console.WriteLine("Total iterations: " + iterations); }