5

I'm creating a chain of responsibility pipeline using System.Func<T, T> where each function in the pipeline holds a reference to the next.

When building the pipeline, I'm unable to pass the inner function by reference as it throws a StackOverflowException due to the reassignment of the pipeline function, for example:

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

I can work around this with a closure:

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");

However, I would like to know if there is a more efficient way to build up this chain of functions, perhaps using Expressions?

Ben Foster
  • 34,340
  • 40
  • 176
  • 285
  • 1
    Just wondering, if you know that the problem is the reassignment of `pipeline` (which is correct), why not just create a new variable? – Camilo Terevinto Mar 17 '18 at 02:21
  • My extra pipeline supports an arbitrary number of functions. Declaring a new variable doesn't help since fundamentally you still have to pass the previous function(s) by reference. – Ben Foster Mar 17 '18 at 07:05
  • Ignore the above, new variable was the solution - I'll blame the lack of sleep :) – Ben Foster Mar 17 '18 at 07:29
  • Glad to help. Do you mind posting your answer with your solution? I prefer that one over the 3 posted answers – Camilo Terevinto Mar 17 '18 at 12:46

3 Answers3

2

What about that? This way you can build chains of arbitrary length.

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;
}
Eugene D. Gubenkov
  • 5,127
  • 6
  • 39
  • 71
  • This is not actually a chain of responsibility pipeline but your answer pointed me to the simple solution of defining a local variable to reference the previous function(s). I should have thought of this earlier. – Ben Foster Mar 17 '18 at 07:19
1

Using expressions, the "building" part of the process is guaranteed to be less efficient because of the cost of compiling the expressions, probably at least two orders of magnitude slower than linking up the Funcs.

Going deep with expressions - where the elements of the pipeline are expressions themselves rather than Funcs, can be used to create a more runtime-efficient implementation by rewriting. Slower to set up, but essentially every time you are handed an expression for an element like:

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

You rewrite it so that the body of whatever is supposed to be in next just gets inlined directly in where next.Invoke(...) appears in the expression tree.

This does limit you to expression-bodied elements though (effectively, you just need to make the body of any complex handlers call a helper function instead of doing whatever work they need to do inline).

Trying to dredge up an example of this out there somewhere, but can't think of a good one off the top of my head. Good luck!

Nicholas Blumhardt
  • 30,271
  • 4
  • 90
  • 101
  • 1
    I found a pretty good post discussing something similar [here](https://particular.net/blog/10x-faster-execution-with-compiled-expression-trees) so may dig into this. – Ben Foster Mar 17 '18 at 07:20
1

Chain of responsibility is like a linked list from my point of view. Normally, it would create a class to encapsulate each handler that contains a reference to the next handler in the chain. But if you want to go with Func style, we could do something similar using procedural style:

Demo here: 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;
    };

What i'm doing is creating a higher order function to build the chain. Another demo to chain 3 handlers using the same idea: https://dotnetfiddle.net/ni0DKL

However, I recommend creating a class for this: https://dotnetfiddle.net/CsVpzh. It's better from encapsulation & abstraction points of view and easy to extend to add specific configurations to each handler.

Khanh TO
  • 48,509
  • 13
  • 99
  • 115
  • Strictly speaking your `Func` versions do not implement chain of responsibility as each handler is not responsible for determining whether the next handler should be executed. I agree regarding the encapsulation of explicit handler types, I just wanted to offer both options since I've able to still provide handler interfaces that essentially fallback to a Func – Ben Foster Mar 17 '18 at 07:24
  • @Ben Foster: It seems that there is no simple solution for implementing chain of responsibilty using `Func`. Because in this pattern, every handler in the chain needs to be `stateful` (store a reference), but a `Func` is `stateless`. The handlers are referenced in the higher order function which determines whether the next handler should be executed, this is functional programming style: https://en.wikipedia.org/wiki/Functional_programming. The accepted answer is also the same idea with a different way of writing. – Khanh TO Mar 17 '18 at 08:30
  • @Ben Foster: If you need different ways to determine whether the next handler should be executed, you could go with writing different chain builders which have different logic to determine how to execute the next handler. IMO, we should go with writing a class like above, that is the most natural way to handle this – Khanh TO Mar 17 '18 at 08:31