Search code examples
c#.netfluent-interface

C# Creating a Fluent API for chaining methods


In C#, we can use Func<> and Action<> types to store what are essentially managed pointers to methods. However, in my experience, they need to be explicitly typed when defined: Func<int> someFunc = myObject.MyMethod;

I am trying to design a fluent API that can chain various methods assuming they have a compatible signature. For instance:

public int IntMethodA( value ) { return value * 2; }
public int IntMethodB( value ) { return value * 4; }
public double DoubleMethod( value ) { return value / 0.5; }

public double ChainMethod( value )
{
  return IntMethodA( value )
            .Then( IntMethodB )
            .Then( DoubleMethod );
}

This is something that is supported by .NET's Task<> class. However, for learning purposes, I am trying to develop something like this from scratch, and it has left me with a few questions:

  1. IntMethodA returns an int. In order to achieve something like this, I would likely need to write an extension method for Func<>, along with all of its possible generic overloads. This means I'll need to cast the initial method as a Func and then return a builder object that can accept subsequent methods. Is there any way I can avoid that initial cast, so as to maintain complete fluency?

  2. Is there a way to automate or make generic the builder methods that accept functions and add them to the chain?

For instance, consider:

public int IntMultiply( int a, int b ) { return a * b; }

public Tuple<double, double> Factor( int value )
{
  /* Some code that finds a set of two numbers that multiply to equal 'value' */
}

These two methods have different signatures and return types. However, if I want to chain IntMultiply().Then( Factor ); it should work, because the input of Factor is the same type as the output of IntMultiply.

However, creating a generic fluent API that can do this seems like a challenge. I would need to be able to somehow take the return type of IntMultiply and constrain any additional methods to accept only that type as the input. Is this even possible to do?

If anyone has insights as to how this project can be approached, or if there are existing projects that do something similar, I would appreciate it.


Solution

  • You could implement something like this:

    public class Fluent<TIn, TOut>
    {
        private readonly TIn _value;
        private readonly Func<TIn, TOut> _func;
    
        public Fluent(TIn value, Func<TIn, TOut> func)
        {
            _value = value;
            _func = func;
        }
    
        public Fluent<TIn, TNewOut> Then<TNewOut>(Func<TOut, TNewOut> func) 
            => new Fluent<TIn, TNewOut>(_value, x => func(_func(x)));
    
        private TOut Calc() => _func(_value);
    
        public static implicit operator TOut(Fluent<TIn, TOut> self) => self.Calc();
    }
    

    then you could chain multiple methods one after another and return what ever you want:

    double f = new Fluent<int, int>(2, x => 2 * x)
        .Then(x => 4 * x)
        .Then(x => x / 0.5);
    Tuple<double, double> t = new Fluent<int, int>(2, x => 2 * x)
        .Then(x => new Tuple<double, double>(x,x));
    

    n.b. You could also remove the overloaded implicit cast operator and make Calc method public. In that case, you could use var because there would be no ambiguity between Fluent<TIn, TOut> and TOut.