简体   繁体   中英

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.

It sounds like you want something like

public static TOut Then<TIn, TOut>(this TIn input, Func<TIn, TOut> func)
{
    return func(input);
}

Then this would work

var result = Multiply(1, 2).Then(Factor);

The idea is that your extension method is not on the first method, but on its result and you can handle the result being anything by making it generic. Then it's just a matter of passing in a Func that takes that value as its input and returns any output you want. Then that output can be passed into the next call to Then with a matching Func . The only down side is that any method you pass into Then can only have one argument, but you can get around that by using tuples or custom classes that your methods return and ingest.

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

nb 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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM