[我正在尝试使用C#中的区别联合(特别是using the excellent OneOf
library)作为表示和执行状态转换的方法,并利用编译器强制的类型安全性和OneOf
的OneOf
方法。
对于有向无环状态转移图,这很好,例如:
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]和D1
至D2
),然后将进入状态D3
的代码重复3次(如E
,E
和d1.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
所以-有没有一种方法可以在状态之间逻辑跳转,而无需满足编译器的
Value
和public 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库)作为表示和执行状态转换的手段,利用了编译器-...
虽然确实是用命令函数的状态来建模状态机[[可以
,但是结果是难以阅读的代码,并且可以通过我在初始文章中举例说明的default
模式进行概括最终代码示例。