В С# мы можем использовать типы 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
возвращает int. Чтобы достичь чего-то подобного, мне, вероятно, потребуется написать метод расширения для Func<>
, а также все его возможные общие перегрузки. Это означает, что мне нужно привести исходный метод как Func
а затем вернуть объект построителя, который может принимать последующие методы. Есть ли способ, которым я могу избежать этого начального броска, чтобы сохранить полную беглость?
Есть ли способ автоматизировать или сделать обобщенные методы компоновщика, которые принимают функции и добавляют их в цепочку?
Например, рассмотрим:
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
.
Тем не менее, создание общего свободного API, который может сделать это, кажется сложной задачей. Мне нужно было бы иметь возможность каким-то образом принимать возвращаемый тип IntMultiply
и ограничивать любые дополнительные методы, чтобы принимать только этот тип в качестве входных данных. Это вообще возможно сделать?
Если у кого-то есть понимание того, как можно подходить к этому проекту, или если есть существующие проекты, которые делают нечто подобное, я был бы признателен.
Вы можете реализовать что-то вроде этого:
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));
nb Вы также можете удалить перегруженный неявный оператор приведения и сделать метод Calc
общедоступным. В этом случае вы можете использовать var
потому что не будет никакой двусмысленности между Fluent<TIn, TOut>
и TOut
.
Похоже, вы хотите что-то вроде
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
. Единственным недостатком является то, что любой метод, который вы передаете Then
может иметь только один аргумент, но вы можете обойти это, используя кортежи или пользовательские классы, которые ваши методы возвращают и принимают.
Then
которые обрабатывают различное количество результатов, как несколько определений Func
? Кроме того, я бы использовал =>
чтобы определить Then
.
Func
, который затем можно использовать с другими значениями? Если последнее, то я бы предложил ограничить методы одним входным аргументом (в любом случае только первый может иметь более одного).