代码之家  ›  专栏  ›  技术社区  ›  Matt Warren

使用“yield”关键字实现状态机

  •  24
  • Matt Warren  · 技术社区  · 15 年前

    使用yield关键字实现简单的状态机是否可行 as shown here . 在我看来,C#编译器已经为您完成了艰苦的工作,因为它在内部实现了一个状态机,以使yield语句工作。

    你能在编译器已经完成的工作的基础上,让它为你实现大部分状态机吗?

    有人这样做过吗?技术上可能吗?

    4 回复  |  直到 15 年前
        1
  •  51
  •   Eric Lippert    15 年前

    这是可行的,但不是一个好主意。迭代器块的创建是为了帮助您为集合编写自定义迭代器,而不是为了解决实现状态机的通用问题。

    如果你想写一个状态机,只需写一个状态机。这并不难。如果您想编写大量的状态机,请编写一个有用的助手方法库,让您能够清晰地表示状态机,然后使用您的库。但是,不要滥用一种语言构造,这种构造的目的完全不同,只是碰巧使用状态机作为实现细节。这使得状态机代码难以阅读、理解、调试、维护和扩展。

    (顺便说一句,我读到你的名字时仔细考虑了一下。C#的一位设计师也叫马特·沃伦!)

        2
  •  8
  •   Mehrdad Afshari    15 年前

    是的,这是绝对可能的,而且很容易做到。您可以享受使用控制流构造的乐趣( for , foreach , while , ... goto (使用 特别适合这种情况;))随着 yield 让我们来建一个。

    IEnumerator<State> StateMachine
                 (Func<int> currentInput /* gets current input from IO port */, 
                  Func<int> currentOutput) {
        for (;;)  {
           if ((currentInput() & 1) == 0) 
               yield return new State("Ready"); 
           else {
               if (...) {
                   yield return new State("Expecting more data");
                   SendOutput(currentOutput());
                   while ((currentInput() & 2) != 0) // while device busy
                        yield return new State("Busy");
               else if (...) { ... } 
           }
        }
    }
    
    // consumer:
    int data;
    var fsm = StateMachine(ReadFromIOPort, () => data);
    // ...
    while (fsm.Current != "Expecting more data")
        fsm.MoveNext();
    data = 100;
    fsm.MoveNext();
    
        3
  •  4
  •   Jon Skeet    15 年前

    迭代器块确实实现了状态机,但棘手的是获取下一个输入。你怎么知道下一步要去哪里?我想你可以有一些共享的“当前转换”变量,但这有点讨厌。

    如果您不需要任何输入(例如,您的状态机只是在状态之间循环),那么这很容易,但这不是有趣的类型:)

    你能描述一下你感兴趣的状态机类型吗?

        4
  •  4
  •   Michael Stum    14 年前

    虽然这不是经典意义上的状态机,但本文 Iterator-based Micro Threading 创造性地将收益用于基于状态的操作。

    IEnumerable Patrol ()
    {
        while (alive){
            if (CanSeeTarget ()) {
                yield return Attack ();
            } else if (InReloadStation){
                Signal signal = AnimateReload ();
                yield return signal;
            } else {
                MoveTowardsNextWayPoint ();
                yield return TimeSpan.FromSeconds (1);
            };
        }
        yield break;
    }