当使用区分工会的Match来表示C#中的状态转换时,如何执行循环状态转换?

问题描述 投票:1回答:1

[我正在尝试使用C#中的区别联合(特别是using the excellent OneOf library)作为表示和执行状态转换的方法,并利用编译​​器强制的类型安全性和OneOfOneOf方法。

对于有向无环状态转移图,这很好,例如:

状态转换图:

Match

状态类型

A -> B -> C1 -> D1 -> E
             -> D2
       -> C2 -> D3

示例状态机功能:

// state-specific constructors, fields and methods removed for brevity:

class A {
    public B Next();
}
class B {
    public OneOf<C1,C2> Next();
}
class C1 {
    public OneOf<D1,D2> Next();
}
class C2 {
    public D3 Next();
}
class D1 {
    public E Next();
}
class D2 {
    public E Next();
}
class D3 {
    public E Next();
}
class E {
    // Terminal state
}

[public E Run( A initialState ) { A a = initialState; B b = a.Next(); return b.Next().Match( ( C1 c1 ) => { return c1.Match( d1 => d1.Next(), d2 => d2.Next() ) }, ( C2 c2 ) => { D3 d3 = c2.Next(); return d3.Next(); } ); } // or, more succinctly: public E Run( A initialState ) { return initialState .Next() // A -> B .Next() // B -> C1 | C2 .Match( c1 => c1.Match( // C1 -> D1 | D2 d1 => d1.Next(), // D1 -> E d2 => d2.Next() // D2 -> E ), c2 => c2 .Next() // C2 -> D3 .Next() // D3 -> E ); } 的使用意味着编译器要求程序明确而详尽地处理所有可能的值类型,而不必依赖于继承/多态性(与原始状态模式一样)。

但是有一些问题:

  • [这实际上是严格的仅前向有向树结构,即使状态机图最终收敛到线性状态转换,因此,如果可以从多个其他先前状态(例如,从.Match(),[ C0]和D1D2),然后将进入状态D3的代码重复3次(如EEd1.Next()呼叫站点所见。)>
  • 此方法不适用于循环
  • 状态转移图,并且大多数状态机都倾向于循环。

    状态转换图:

考虑显示循环的状态转换图(由重复的节点名称表示-我不擅长ASCII艺术,例如:

d2.Next()

以及这些状态类型:

d3.Next()

...如果我将同范围的A -> B -> C1 -> D -> E -> A -> C2 -> B 语句与class A { public B Next(); } class B { public OneOf<C1,C2> Next(); } class C1 { public OneOf<D,A> Next(); } class C2 { public B Next(); } class D { public E Next(); } class E { // Terminal state } 而不是if一起使用(这意味着我们将丢失编译器强制的详尽检查),而必须使用OneOf.TryPick(令人恐惧):

OneOf.Match

这很丑陋-从使用goto到必要的public E Run( A initialState ) { A a; stateA: a = initialState; stateB: B b; b = a.Next(); OneOf<C1,C2> bNext = b.Next(); if( bNext.TryPickT0( out C1 c1, out _ ) ) { OneOf<D,A> c1Next = c1.Next(); if( c1Next.TryPickT0( out D d, out _ ) ) { return d.Next(); } else if( c1Next.TryPickT1( out a, out _ ) ) { goto stateA; } else { throw new InvalidOperationException(); } } else if( b.Next.TryPickT1( out C2 c2, out _ ) ) { b = c2.Next(); goto stateB; } else { throw new InvalidOperationException(); } } 部分,以防止编译器抱怨可能的返回值-但这(唯一)的好处是将程序流完全保留在goto函数内避免更改对象实例状态(与仅更改作用域内的局部变量相反,使其具有固有的线程安全性)-这在else { throw代码中也具有优势,因为代表Run状态机的对象保持了简单。 >

[通过使用带有枚举类型的async存在替代方法(这很糟糕,因为我不想维护async来表示已经定义的状态类)-或C#7.0模式匹配switch(以需要向下转换为enum并使用运行时类型信息以使switch正常运行为代价,并且编译器不会验证该切换是否详尽无遗,因此其他程序员可以添加新状态)并且下面的代码仍然可以编译(因为Object调用已替换为switch,因为Match的每个成员的lambda只会返回状态值):

Match

所以-有没有一种方法可以在状态之间逻辑跳转,而无需满足编译器的Valuepublic E Run( A initialState ) { Object state = initialState; while( true ) { switch( state ) { case A a: state = a.Next(); break; case B b: state = b.Next().Value; break; case C1 c1: state = c1.Next().Value; break; case C2 c2: state = c2.Next().Value; break; case D d: state = d.Next().Value; break; case E e: return e; default: throw new InvalidOperationException( "Unknown state: " + state?.ToString() ?? "null" ); } } } 情况?

我正在尝试在C#中使用区分的并集(特别是使用出色的OneOf库)作为表示和执行状态转换的手段,利用了编译器-...

c# design-patterns state state-machine
1个回答
0
投票

虽然确实是用命令函数的状态来建模状态机[[可以

,但是结果是难以阅读的代码,并且可以通过我在初始文章中举例说明的default模式进行概括最终代码示例。
© www.soinside.com 2019 - 2024. All rights reserved.