C#为链接方法创建Fluent API

时间:2019-02-08 19:50:35

标签: c# .net fluent-interface

在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<>类支持此功能。但是,出于学习目的,我正在尝试从头开始开发类似的东西,这给我留下了一些问题:

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

  2. 是否有一种方法可以自动化或通用化接受函数并将其添加到链中的builder方法?

例如,考虑:

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

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

但是,创建可以做到这一点的通用fluent API似乎是一个挑战。我将需要能够以某种方式采用IntMultiply的返回类型,并约束任何其他方法以仅接受该类型作为输入。这甚至有可能吗?

如果有人对如何实施该项目有深入的了解,或者有现有的项目在做类似的事情,我将不胜感激。

2 个答案:

答案 0 :(得分:2)

听起来您想要类似

index.html

然后这将起作用

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

这个想法是您的扩展方法不在第一个方法上,而是在它的结果上,您可以通过使其通用来处理结果。然后,只需传递一个var result = Multiply(1, 2).Then(Factor); 并将其作为输入并返回所需的任何输出即可。然后可以将该输出传递到具有匹配的Func的下一次对Then的调用中。唯一的缺点是,传递给Func的任何方法只能有一个参数,但是可以使用方法返回并提取的元组或自定义类来解决该问题。

答案 1 :(得分:1)

您可以实现以下内容:

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

然后,您可以一个接一个地链接多个方法,并返回您想要的内容:

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方法成为公共方法。在这种情况下,您可以使用var,因为Fluent<TIn, TOut>TOut之间不会有歧义。