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

Jus*_*ant 6 .net c# functional-programming idisposable using

我们的服务器应用程序有几个方法,按顺序调用,遍历20M行结果集并对其进行转换.此管道中的每个方法都存储200多兆字节的数据副本,可预测的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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

我想将其转换为更加实用的解决方案,迭代原始源数据,而无需将整个数据集保存在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;
Run Code Online (Sandbox Code Playgroud)

但是今天,每个单独的管道步骤都有自己昂贵的设置和拆除过程,这些过程是通过using块强制执行的.

重构这些管道方法的好方法是什么,以确保设置/拆除代码?在伪代码中,我想最终得到这个:

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

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

我知道我可以usingtry/ 手动替换块finally,但是为多个资源手动替换它似乎比必要更难.

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

似乎这是一个比我已经想到的更聪明的模式,所以在重新发明轮子之前问这里.

Mat*_*ted 3

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

... 输出 ...

{ 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
Run Code Online (Sandbox Code Playgroud)

这是一个管道示例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,
            };
Run Code Online (Sandbox Code Playgroud)

...结果...

Disposed: 1/0
{ x = 0, r = 8 }
Disposed: 1/1
{ x = 1, r = 16 }
Disposed: 0/0
Run Code Online (Sandbox Code Playgroud)

...示例的测试类...

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; }
}
Run Code Online (Sandbox Code Playgroud)