C # Создание Fluent API для методов цепочки

2

В С# мы можем использовать типы 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 возвращает int. Чтобы достичь чего-то подобного, мне, вероятно, потребуется написать метод расширения для Func<>, а также все его возможные общие перегрузки. Это означает, что мне нужно привести исходный метод как Func а затем вернуть объект построителя, который может принимать последующие методы. Есть ли способ, которым я могу избежать этого начального броска, чтобы сохранить полную беглость?

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

Эти два метода имеют разные подписи и возвращаемые типы. Однако, если я хочу IntMultiply().Then( Factor ); это должно работать, потому что ввод Factor того же типа, что и выход IntMultiply.

Тем не менее, создание общего свободного API, который может сделать это, кажется сложной задачей. Мне нужно было бы иметь возможность каким-то образом принимать возвращаемый тип IntMultiply и ограничивать любые дополнительные методы, чтобы принимать только этот тип в качестве входных данных. Это вообще возможно сделать?

Если у кого-то есть понимание того, как можно подходить к этому проекту, или если есть существующие проекты, которые делают нечто подобное, я был бы признателен.

  • 0
    Вы хотите, чтобы результат стал фактическим значением после вызова всех методов по порядку, или вы пытаетесь создать Func , который затем можно использовать с другими значениями? Если последнее, то я бы предложил ограничить методы одним входным аргументом (в любом случае только первый может иметь более одного).
  • 0
    @juharr Бывший. Выходы передаются через цепочечные методы, и что бы ни выводил последний метод, это будет то, что будет возвращено при выполнении всей цепочки.
Теги:
fluent-interface

2 ответа

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

nb Вы также можете удалить перегруженный неявный оператор приведения и сделать метод Calc общедоступным. В этом случае вы можете использовать var потому что не будет никакой двусмысленности между Fluent<TIn, TOut> и TOut.

2

Похоже, вы хотите что-то вроде

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 может иметь только один аргумент, но вы можете обойти это, используя кортежи или пользовательские классы, которые ваши методы возвращают и принимают.

  • 0
    Или, возможно, путем создания нескольких определений для Then которые обрабатывают различное количество результатов, как несколько определений Func ? Кроме того, я бы использовал => чтобы определить Then .
  • 0
    Конечно, вы не можете вернуть несколько типов из одного метода, так что, возможно, в этом нет необходимости?

Ещё вопросы

Сообщество Overcoder
Наверх
Меню