使用Func的责任链

时间:2018-03-17 01:57:27

标签: c# chain-of-responsibility

我正在使用System.Func<T, T>创建一个责任链管道,其中管道中的每个函数都包含对下一个函数的引用。

构建管道时,我无法通过引用传递内部函数,因为它会因管道函数的重新分配而抛出StackOverflowException,例如:

Func<string, Func<string, string>, string> handler1 = (s, next) => {
    s = s.ToUpper();
    return next.Invoke(s);
};

Func<string, string> pipeline = s => s;
pipeline = s => handler1.Invoke(s, pipeline);

pipeline.Invoke("hello"); // StackOverFlowException

我可以通过闭包来解决这个问题:

Func<string, Func<string, string>, string> handler1 = (s, next) => {
    s = s.ToUpper();
    return next.Invoke(s);
};

Func<Func<string, string>, Func<string, string>> closure = 
    next => s => handler1.Invoke(s, next);

Func<string, string> pipeline = s => s;
pipeline = closure.Invoke(pipeline);

pipeline.Invoke("hello");

但是,我想知道是否有更有效的方法来构建这个函数链,可能使用表达式?

3 个答案:

答案 0 :(得分:1)

使用表达式,&#34;建立&#34;由于编译表达式的成本,这个过程的一部分保证效率较低,可能比链接Func的速度慢至少两个数量级。

深入研究表达式 - 管道的元素本身是表达式而不是Func,可用于通过重写创建更具运行时效率的实现。设置较慢,但基本上每次给你一个元素的表达式,如:

Expression<Func<string, Func<string, string>, string>> handler1 = (s, next) => 
    next.Invoke(s.ToUpper());

你重写它,以便next中应该包含的内容直接在表达式树中出现next.Invoke(...)的位置直接内联。

这确实限制了你对表达式身体元素的影响(实际上,你只需要让任何复杂处理程序的主体调用辅助函数而不是做他们需要内联的任何工作)。

试图在某个地方挖掘一个这样的例子,但是不能想到一个好的东西。祝你好运!

答案 1 :(得分:1)

从我的角度来看,责任链就像一个链表。通常,它会创建一个类来封装每个处理程序,该处理程序包含对链中下一个处理程序的引用。但是如果你想使用Func样式,我们可以使用程序样式做类似的事情:

在这里演示:https://dotnetfiddle.net/LrlaRm

public static void Main()
    {
        Func<string, string> handler1 = (s) => {
            s = s.ToUpper();
            return s;
        };

        Func<string, string> handler2 = (s) => {
            s = s.TrimStart();
            return s;
        };


        Func<string, string> chain = ChainBuilder(handler1, handler2);

        Console.WriteLine(chain("    hello"));
    }

    static Func<Func<string, string>, Func<string, string>, Func<string, string>> ChainBuilder = (f1, f2) => s => {
        s = f1(s);
        if (f2 != null) {
            s = f2(s);
        }

        return s;
    };

我正在做的是创建一个更高阶的函数来构建链。 另一个使用相同想法链接3个处理程序的演示:https://dotnetfiddle.net/ni0DKL

但是,我建议为此创建一个类:https://dotnetfiddle.net/CsVpzh。封装和封装效果更好抽象的观点,易于扩展,为每个处理程序添加特定的配置。

答案 2 :(得分:1)

那怎么样?这样你就可以构建任意长度的链。

void Main()
{
    Func<string, string> f1 = x => x.Replace("*", string.Empty);
    Func<string, string> f2 = x => x.Replace("--", string.Empty);
    Func<string, string> f3 = x => x.ToUpper();

    //Func<string, string> pipeline = x => f3(f2(f1(x)));
    Func<string, string> pipeline = Pipeline(f1, f2, f3);

    pipeline.Invoke("te-*-st").Dump(); // prints "TEST"
}

Func<T, T> Pipeline<T>(params Func<T, T>[] functions)
{
    Func<T, T> resultFn = x => x;

    for (int i = 0; i < functions.Length; i++)
    {
        Func<T, T> f = functions[i];
        Func<T, T> fPrev = resultFn;
        resultFn = x => f(fPrev(x));
    }

    return resultFn;
}