C# 为链接方法创建 Fluent API

C# Creating a Fluent API for chaining methods

在 C# 中,我们可以使用 Func<>Action<> 类型来存储本质上是指向方法的托管指针。但是,根据我的经验,它们需要在定义时显式输入:Func<int> someFunc = myObject.MyMethod;

我正在尝试设计一个流畅的 API,假设它们具有兼容的签名,它可以链接各种方法。例如:

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

.NET Task<> class 支持此功能。但是,出于学习目的,我正在尝试从头开始开发这样的东西,它给我留下了几个问题:

  1. IntMethodA return 是一个整数。为了实现这样的目标,我可能需要为 Func<> 及其所有可能的泛型重载编写一个扩展方法。 这意味着我需要将初始方法转换为 Func,然后 return 一个可以接受后续方法的构建器对象。 我有什么办法可以避免初始转换,以保持完全流畅?

  2. 有没有办法自动化或通用化接受函数并将它们添加到链中的构建器方法?

例如,考虑:

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' */
}

这两种方法具有不同的签名和 return 类型。但是,如果我想链接 IntMultiply().Then( Factor ); 它应该可以工作,因为 Factor 的输入与 IntMultiply.

的输出类型相同

但是,创建可以做到这一点的通用流利 API 似乎是一个挑战。我需要能够以某种方式采用 IntMultiply 的 return 类型,并限制任何其他方法只接受该类型作为输入。这甚至可以做到吗?

如果有人对如何处理这个项目有任何见解,或者是否有现有项目在做类似的事情,我将不胜感激。

听起来你想要

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

这样就可以了

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

想法是您的扩展方法不在第一个方法上,而是在其结果上,您可以通过将其设为通用来处理任何结果。然后,只需传入一个 Func 并将该值作为其输入并 return 任何您想要的输出即可。然后可以将该输出传递到下一次调用 Then 并匹配 Func。唯一的缺点是你传递给 Then 的任何方法只能有一个参数,但你可以通过使用你的方法 return 和摄取的元组或自定义 类 来解决这个问题。

你可以这样实现:

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

然后您可以一个接一个地链接多个方法,return随心所欲:

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。您还可以删除重载的隐式转换运算符并使 Calc 方法成为 public。在这种情况下,您可以使用 var 因为 Fluent<TIn, TOut>TOut.

之间不会有歧义