使用块分解 C# 以启用函数式编程的模式



我们的服务器应用有几个按顺序调用的方法,这些方法循环访问 20M 行的结果集并对其进行转换。此管道中的每个方法都存储 200+ MB 的数据副本,可预见的是 RAM 和 GC 性能影响。

每种方法都遵循类似的模式:

public HugeCollection1 Step1 (SomeType sourceData)
{ 
var transformed = new List<RowType>;
using (var foo = InitializeSomethingExpensive(sourceData))
{
foreach (var row in foo)
{
transformed.Add (TransformRow(row));
}
}
return transformed;
}

然后在管道中调用这些方法,例如

var results1 = Step1(sourceData);
var results2 = Step2(results1);
var results3 = Step3(results2);
...
var finalResults = StepN (resultsNMinus1);
return finalResults; // final results

我想将其转换为功能更强大的解决方案,该解决方案遍历原始源数据,而无需将整个数据集保存在RAM中。 我想最终得到一个没有任何中间集合的最终结果列表。

如果在管道的每个阶段都不需要设置,那么解决方案将很简单:只需为每一行运行每个转换并仅存储最终结果。

var transformed = new List<SmallResult>;
// TODO: How to set up and ensure teardown of the *other* pipeline steps?
using (var foo = InitializeSomethingExpensive(sourceData))
{
foreach (var row in foo)
{
object result = row;
foreach (var step in Pipeline)
{
result = step.Transform (result);
}
transformed.Add (result as SmallResult);
}
}
return transformed;

但是今天,这些单独的管道步骤中的每一个都有其昂贵的设置和拆卸过程,这些过程是通过using块强制执行的。

重构这些管道方法中的每一个以保证设置/拆卸代码的好模式是什么? 在伪代码中,我想以这个结束:

  1. 设置所有步骤
  2. 遍历每一行
  3. 通过每个步骤转换行
  4. 结束循环
  5. 清理所有步骤,确保清理始终发生
  6. 返回(小(结果

将所有 using 块合并到一个方法中是不切实际的,因为每个步骤中的代码都很长且共享,我不想在一个方法中重复该共享代码。

我知道我可以手动将using块替换为try/finally,但手动为多个资源执行此操作似乎比必要的更难。

是否有更简单的解决方案,例如以智能方式同时使用usingyield? 或者是否有一个好的"多用途"类实现,使这个协调的设置/拆卸过程变得容易(例如,它的构造函数接受返回 IDisposable 的函数列表,它的 Dispose(( 实现将确保一切都被清理(?

似乎这是比我聪明的人已经想出的模式,所以在重新发明轮子之前先在这里询问。

我不确定为什么要创建这么多一次性对象(您可以使用可生成的方法清理这些对象(,但您可以创建一个扩展方法来为您清理此模式

public static class ToolsEx
{
public static IEnumerable<T> EnumerateAndDispose<X, T>(this X input, 
Func<X, IEnumerable<T>> func)
where X : IDisposable
{
using (var mc = input)
foreach (var i in func(mc))
yield return i;
}
}

你可以像这样使用它...

var query = from x in new MyClass(0, 0, 2).EnumerateAndDispose(i => i)
from y in new MyClass(1, x, 3).EnumerateAndDispose(i => i)
select new
{
x,
y,
};
foreach (var i in query)
Console.WriteLine(i);

。输出。。。

{ x = 0, y = 0 }
{ x = 0, y = 1 }
{ x = 0, y = 2 }
Disposed: 1/0
{ x = 1, y = 0 }
{ x = 1, y = 1 }
{ x = 1, y = 2 }
Disposed: 1/1
Disposed: 0/0

这是一个管道示例,其中包含Aggregate...

var query = from x in new MyClass(0, 0, 2).EnumerateAndDispose(i => i)
let r = new MyClass(1, x, 3).EnumerateAndDispose(i => i)
.Aggregate(x, (a, i) => (a + i) * 2)
select new
{
x,
r,
};

。和结果...

Disposed: 1/0
{ x = 0, r = 8 }
Disposed: 1/1
{ x = 1, r = 16 }
Disposed: 0/0

。示例的测试类...

public class MyClass : IEnumerable<int>, IDisposable
{
public MyClass(int set, int set2, int size)
{
this.Size = size;
this.Set = set;
this.Set2 = set2;
}
public IEnumerator<int> GetEnumerator()
{
foreach (var i in Enumerable.Range(0, this.Size))
yield return i;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public void Dispose()
{
Console.WriteLine("Disposed: {0}/{1}", this.Set, this.Set2);
}
public int Size { get; private set; }
public int Set { get; private set; }
public int Set2 { get; private set; }
}

最新更新