在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<>
类支持此功能。但是,出于学习目的,我正在尝试从头开始开发类似的东西,这给我留下了一些问题:
IntMethodA
返回一个整数。为了实现这样的目标,我可能需要为Func<>
编写一个扩展方法,以及所有可能的泛型重载。 这意味着我需要将初始方法转换为Func
,然后返回一个可以接受后续方法的生成器对象。我有什么办法可以避免这种初始转换,因此保持完全流畅?
是否有一种方法可以自动化或通用化接受函数并将其添加到链中的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
的返回类型,并约束任何其他方法以仅接受该类型作为输入。这甚至有可能吗?
如果有人对如何实施该项目有深入的了解,或者有现有的项目在做类似的事情,我将不胜感激。
答案 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
之间不会有歧义。