4

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.

2
  • Do you want the result to be the actual value after calling all the methods in order or are you trying to create a Func that you can then reuse with different values? If the latter then I'd suggest limiting the methods to one input argument (only the first one could every have more than one anyway). Commented Feb 8, 2019 at 20:10
  • @juharr The former. Outputs are passed down through the chained methods, and whatever the last method outputs is what would be returned when the entire chain is executed. Commented Feb 8, 2019 at 20:16

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

Comments

3

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.

2 Comments

Or perhaps by creating multiple definitions for Then that handle varying number of results, like the multiple Func definitions? Also, I would use => to define Then.
Of course, you can't return multiple types from a single method, so perhaps that isn't necessary?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.