Search code examples
c#chain-of-responsibility

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?


Solution

  • 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;
    }