Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chain of responsibility using Func

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?

like image 366
Ben Foster Avatar asked Oct 17 '22 21:10

Ben Foster


3 Answers

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;
}
like image 147
Eugene D. Gubenkov Avatar answered Oct 20 '22 22:10

Eugene D. Gubenkov


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!

like image 36
Nicholas Blumhardt Avatar answered Oct 20 '22 21:10

Nicholas Blumhardt


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.

like image 20
Khanh TO Avatar answered Oct 20 '22 20:10

Khanh TO