C# 为方法调用构建 Fluent API

发布于 2024-11-13 23:39:05 字数 1932 浏览 1 评论 0原文

我必须做什么才能说 InvokeMethod 可以调用一个方法,并且当使用 Repeat 等特殊选项时,它将在 Repeat 之后执行。

我现在的问题是,该方法在知道必须调用 100 次之前就已经执行了。

class Program
{
    static void Main()
    {
        const bool shouldRun = true;

        new MethodExecuter()
            .ForAllInvocationsUseCondition(!Context.WannaShutDown)
                .InvokeMethod(A.Process).Repeat(100)
                .When(shouldRun).ThenInvokeMethod(B.Process).Repeat(10)
            .ForAllInvocationsUseCondition(Context.WannaShutDown)
                .When(shouldRun).ThenInvokeMethod(C.Process);
    }
}

方法表达式

public class MethodExpression
{
    private bool _isTrue = true;
    private readonly MethodExecuter _methodExecuter;
    public MethodExpression(bool isTrue, MethodExecuter methodExecuter)
    {
        _isTrue = isTrue;
        _methodExecuter = methodExecuter;
    }

    public MethodExecuter ThenInvokeMethod(Action action)
    {
        if (_isTrue)
        {
            action.Invoke();
            _isTrue = false;
        }
        return _methodExecuter;
    }
}

方法执行器

public class MethodExecuter
{
    private bool _condition;
    private int _repeat = 1;

    public MethodExpression When(bool isTrue)
    {
        return new MethodExpression(isTrue && _condition, this);
    }

    public MethodExecuter InvokeMethod(Action action)
    {
        if (_condition)
        {
            for (int i = 1; i <= _repeat; i++)
            {
                action.Invoke();
            }
        }
        return this;
    }

    public MethodExecuter ForAllInvocationsUseCondition(bool condition)
    {
        _condition = condition;
        return this;
    }

    public MethodExecuter Repeat(int repeat)
    {
        _repeat = repeat;
        return this;
    }
}

What do I have to do to say that InvokeMethod can invoke a method and when using special options like Repeat it shall exexute after the Repeat.

My problem for now is that the method will already exexute before it knows that it has to be called 100 times.

class Program
{
    static void Main()
    {
        const bool shouldRun = true;

        new MethodExecuter()
            .ForAllInvocationsUseCondition(!Context.WannaShutDown)
                .InvokeMethod(A.Process).Repeat(100)
                .When(shouldRun).ThenInvokeMethod(B.Process).Repeat(10)
            .ForAllInvocationsUseCondition(Context.WannaShutDown)
                .When(shouldRun).ThenInvokeMethod(C.Process);
    }
}

MethodExpression

public class MethodExpression
{
    private bool _isTrue = true;
    private readonly MethodExecuter _methodExecuter;
    public MethodExpression(bool isTrue, MethodExecuter methodExecuter)
    {
        _isTrue = isTrue;
        _methodExecuter = methodExecuter;
    }

    public MethodExecuter ThenInvokeMethod(Action action)
    {
        if (_isTrue)
        {
            action.Invoke();
            _isTrue = false;
        }
        return _methodExecuter;
    }
}

MethodExecuter

public class MethodExecuter
{
    private bool _condition;
    private int _repeat = 1;

    public MethodExpression When(bool isTrue)
    {
        return new MethodExpression(isTrue && _condition, this);
    }

    public MethodExecuter InvokeMethod(Action action)
    {
        if (_condition)
        {
            for (int i = 1; i <= _repeat; i++)
            {
                action.Invoke();
            }
        }
        return this;
    }

    public MethodExecuter ForAllInvocationsUseCondition(bool condition)
    {
        _condition = condition;
        return this;
    }

    public MethodExecuter Repeat(int repeat)
    {
        _repeat = repeat;
        return this;
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

情栀口红 2024-11-20 23:39:05

使用最终方法(“go”或“execute”)来真正开始工作。

      new MethodExecuter()
        .ForAllInvocationsUseCondition(!Context.WannaShutDown)
            .InvokeMethod(A.Process).Repeat(100)
            .When(shouldRun).ThenInvokeMethod(B.Process).Repeat(10)
        .ForAllInvocationsUseCondition(Context.WannaShutDown)
            .When(shouldRun).ThenInvokeMethod(C.Process)
            .Go();

Use a final method ("go", or "execute") to actually kick things off.

      new MethodExecuter()
        .ForAllInvocationsUseCondition(!Context.WannaShutDown)
            .InvokeMethod(A.Process).Repeat(100)
            .When(shouldRun).ThenInvokeMethod(B.Process).Repeat(10)
        .ForAllInvocationsUseCondition(Context.WannaShutDown)
            .When(shouldRun).ThenInvokeMethod(C.Process)
            .Go();
你丑哭了我 2024-11-20 23:39:05

您提供的内容看起来有点像对工作流程或状态机进行编程。为了在执行期间捕获调用并遵守条件,您需要稍微改变您的方法。

考虑将操作推入队列,然后提供运行状态机的机制,而不是在操作进入时调用操作。

new MethodInvoker()
       .ForAllInvocationsUseCondition(true)
           .InvokeMethod( Process.A ).Repeat(100)
       .Run();

What you've provided looks a bit like programming a workflow or state machine. In order to capture invocations and respect conditions during execution, you'd need to change your approach slightly.

Instead of invoking actions as they come in, consider pushing your actions into a queue and then providing an mechanism to run the state machine.

new MethodInvoker()
       .ForAllInvocationsUseCondition(true)
           .InvokeMethod( Process.A ).Repeat(100)
       .Run();
赠佳期 2024-11-20 23:39:05

有很多方法可以给这只猫剥皮,但我认为这种困难的根源之一是您实际上在 InvokeMethod() 方法中调用了该方法(看图!)。

通常,我们使用流畅的 API 将从内到外评估的语法转换为可以从左到右的方式表达的语法。因此,接口的表达式构建器组件用于在整个表达式中构建状态,并且只有在最后才会发生“真正的工作”。

解决当前问题的一种解决方案是将每个操作及其相关选项(调用条件、重复计数等)排队,并向 MethodExecuter 添加一些 ExecuteAll() 方法在成员链末端出列并执行完全配置的操作。

另一种解决方案是将所有执行选项放在 InvokeMethod() 方法中;类似于:

.Invoke(x => x.Method(A.Process).Repeat(100))

此方法看起来类似于:

public MethodExecuter Invoke(Action<IExecutionBuilder> executionBuilder)
 {
     var builder = new ExecutionBuilder();
      executionBuilder(builder);

      var action = builder.Action; 
      var repeat = builder.RepeatCount;

      if (_condition)
      {
          for (int i = 1; i <= repeat; i++)
            {
                action();
            }
      }

      return this;
 }

我还没有在 Visual Studio 中完成此操作,但其他项目类似于:

public interface IExecutionBuilder
 {
     IExecutionBuilder Method(Action action);
      IExecutionBuilder Repeat(int count);
 }

 public class ExecutionBuilder : IExecutionBuilder
 {
      public ExecutionBuilder()
      {
          RepeatCount = 1; // default to repeat once
            Action = () => {}; // default to do nothing, but not null
      }

     public IExecutionBuilder Method(Action action)
      {
          Action = action;
          return this;
      }

      public IExecutionBuilder Repeat(int repeat)
      {
          RepeatCount = repeat;
                  return this;
      }

      public int RepeatCount { get; private set; }
      public Action Action { get; private set; }
 }

请注意,RepeatCountAction 是不暴露在接口上。这样,您在调用 .Invoke(x => x. 时将看不到这些成员,但在使用 ExecutionBuilder 内的具体 ExecutionBuilder 类时可以访问它们。代码>Invoke() 方法。

There are a lot of ways to skin this cat, but I think one source of this difficulty is in the fact that you actually invoke the method within the InvokeMethod() method (go figure!).

Typically, we use fluent APIs to turn syntax that is evaluated from the inside-out into something that can be expressed in a left-to-right fashion. Thus, the expression builder components of the interface are used to build up state throughout the expression, and only at the end does the "real work" happen.

One solution to your immediate problem is to queue up each action with its associated options (invocation conditions, repeat count, etc.), and add some ExecuteAll() method to MethodExecuter that dequeues and executes the fully configured actions at the end of the member chain.

Another solution would be to put all of the execution options inside the InvokeMethod() method; something like:

.Invoke(x => x.Method(A.Process).Repeat(100))

This method would look something like:

public MethodExecuter Invoke(Action<IExecutionBuilder> executionBuilder)
 {
     var builder = new ExecutionBuilder();
      executionBuilder(builder);

      var action = builder.Action; 
      var repeat = builder.RepeatCount;

      if (_condition)
      {
          for (int i = 1; i <= repeat; i++)
            {
                action();
            }
      }

      return this;
 }

I haven't worked through this in Visual Studio, but the other items would be something like:

public interface IExecutionBuilder
 {
     IExecutionBuilder Method(Action action);
      IExecutionBuilder Repeat(int count);
 }

 public class ExecutionBuilder : IExecutionBuilder
 {
      public ExecutionBuilder()
      {
          RepeatCount = 1; // default to repeat once
            Action = () => {}; // default to do nothing, but not null
      }

     public IExecutionBuilder Method(Action action)
      {
          Action = action;
          return this;
      }

      public IExecutionBuilder Repeat(int repeat)
      {
          RepeatCount = repeat;
                  return this;
      }

      public int RepeatCount { get; private set; }
      public Action Action { get; private set; }
 }

Note that RepeatCount and Action are not exposed on the interface. This way, you will not see these members when calling .Invoke(x => x., but will have access to them when using the concrete ExecutionBuilder class inside the Invoke() method.

萤火眠眠 2024-11-20 23:39:05

您可以有一个 SetInvokeMethod 和一个 Execute 方法。

SetInvokeMethod(Action).Repeat(100).Execute()

You could have a SetInvokeMethod and an Execute Method.

SetInvokeMethod(Action).Repeat(100).Execute()
森林很绿却致人迷途 2024-11-20 23:39:05

一句话,你的代码太“渴望”了。在流畅的语法结构告诉代码应该重复多少次之前,会调用 InvokeMethod 方法并执行操作。

要更改此设置,请尝试将要调用的方法指定为 methodInvoker 中的私有字段,然后包含一个作为实际执行命令的“触发器”的命令。关键是“懒惰评价”;在流畅的界面中,除非必要,否则什么都不应该做;这样你就可以控制事情发生的时间。

public class FluentMethodInvoker
{
    Predicate condition = ()=>true;
    Predicate allCondition = null;
    Action method = ()=> {return;};
    bool iterations = 1;
    FluentMethodInvoker previous = null;

    public FluentMethodInvoker(){}
    private FluentMethodInvoker(FluentMethodInvoker prevNode)
    { previous = prevNode; }

    public FluentMethodInvoker InvokeMethod(Action action)
    {
        method = action;
    }

    //Changed "When" to "If"; the function does not wait for the condition to be true
    public FluentMethodInvoker If(Predicate pred)
    {
        condition = pred;
        return this;
    }

    public FluentMethodInvoker ForAllIf(Predicate pred)
    {
        allCondition = pred;
        return this;
    }

    private bool CheckAllIf()
    {
        return allCondition == null 
                ? previous == null
                   ? true
                   : previous.CheckAllIf();
                : allCondition;
    }

    public FluentMethodInvoker Repeat(int repetitions)
    {
        iterations = repetitions;
        return this;
    }

    //Merging MethodExecuter and MethodExpression, by chaining instances of FluentMethodInvoker
    public FluentMethodInvoker Then()
    {
        return new FluentMethodInvoker(this);
    }

    //Here's your trigger
    public void Run()
    {
        //goes backward through the chain to the starting node
        if(previous != null) previous.Run();

        if(condition && CheckAllIf())    
           for(var i=0; i<repetitions; i++)
              method();

        return;
    }
}

//usage
class Program
{
    static void Main()
    {
        const bool shouldRun = true;

        var invokerChain = new FluentMethodInvoker()
            .ForAllIf(!Context.WannaShutDown)
                .InvokeMethod(A.Process).Repeat(100)
                .When(shouldRun)
            .Then().InvokeMethod(B.Process).Repeat(10)
            .ForAllIf(Context.WannaShutDown)
                .When(shouldRun)
            .Then().InvokeMethod(C.Process);

        //to illustrate that the chain doesn't have to execute immediately when being built
        invokerChain.Run();
    }
}

In a sentence, your code is too "eager". The InvokeMethod method is called, and performs the action, before your fluent grammar structure tells your code how many times it should repeat.

To change this, try also specifying the method you are invoking as a private field in your methodInvoker, then include a command that is a "trigger" to actually perform the commands. The key is "lazy evaluation"; in a fluent interface, nothing should be done until it has to; that way you have most of the control over when it does happen.

public class FluentMethodInvoker
{
    Predicate condition = ()=>true;
    Predicate allCondition = null;
    Action method = ()=> {return;};
    bool iterations = 1;
    FluentMethodInvoker previous = null;

    public FluentMethodInvoker(){}
    private FluentMethodInvoker(FluentMethodInvoker prevNode)
    { previous = prevNode; }

    public FluentMethodInvoker InvokeMethod(Action action)
    {
        method = action;
    }

    //Changed "When" to "If"; the function does not wait for the condition to be true
    public FluentMethodInvoker If(Predicate pred)
    {
        condition = pred;
        return this;
    }

    public FluentMethodInvoker ForAllIf(Predicate pred)
    {
        allCondition = pred;
        return this;
    }

    private bool CheckAllIf()
    {
        return allCondition == null 
                ? previous == null
                   ? true
                   : previous.CheckAllIf();
                : allCondition;
    }

    public FluentMethodInvoker Repeat(int repetitions)
    {
        iterations = repetitions;
        return this;
    }

    //Merging MethodExecuter and MethodExpression, by chaining instances of FluentMethodInvoker
    public FluentMethodInvoker Then()
    {
        return new FluentMethodInvoker(this);
    }

    //Here's your trigger
    public void Run()
    {
        //goes backward through the chain to the starting node
        if(previous != null) previous.Run();

        if(condition && CheckAllIf())    
           for(var i=0; i<repetitions; i++)
              method();

        return;
    }
}

//usage
class Program
{
    static void Main()
    {
        const bool shouldRun = true;

        var invokerChain = new FluentMethodInvoker()
            .ForAllIf(!Context.WannaShutDown)
                .InvokeMethod(A.Process).Repeat(100)
                .When(shouldRun)
            .Then().InvokeMethod(B.Process).Repeat(10)
            .ForAllIf(Context.WannaShutDown)
                .When(shouldRun)
            .Then().InvokeMethod(C.Process);

        //to illustrate that the chain doesn't have to execute immediately when being built
        invokerChain.Run();
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文