27

使用 yield 关键字来实现一个简单的状态机是否可行,如此处所示。在我看来,C# 编译器似乎为您完成了艰苦的工作,因为它在内部实现了一个状态机来使 yield 语句工作。

你能在编译器已经在做的工作之上搭载它并让它为你实现大部分状态机吗?

有人做过吗,技术上可行吗?

4

4 回答 4

52

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

如果你想写一个状态机,就写一个状态机。这并不难。如果您想编写大量状态机,请编写一个有用的帮助方法库,让您清楚地表示状态机,然后使用您的库。但是不要滥用一种语言结构,该结构旨在用于完全不同的东西,而恰好使用状态机作为实现细节。这使您的状态机代码难以阅读、理解、调试、维护和扩展。

(顺便说一句,当我读到你的名字时,我做了一个双重考虑。C# 的设计者之一也叫 Matt Warren!)

于 2009-07-28T16:23:14.827 回答
8

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

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();
于 2009-07-28T15:22:24.773 回答
4

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

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

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

于 2009-07-28T15:24:14.847 回答
4

虽然这不是经典意义上的状态机,但关于基于迭代器的微线程的文章创造性地使用了 yield 用于基于状态的操作。

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;
}
于 2010-04-08T00:14:53.513 回答