隐含的类型转换和多态性

时间:2017-03-28 21:18:17

标签: c#

我目前正在为状态机构建一个小型库(是的,有很多这样的库,但我很喜欢它)。现在,我遇到了一个有趣的问题。

假设给定的状态机接受某种类型的事件。如果我们要解析协议,这可能是是人物。因此,在某种状态下,字符“a”可能会导致新状态,而所有其他字符(或一组字符)仍然处于该状态(例如,接收数据包的有效负载,而没有收到ETX)。

我的状态机定义类似于Boost :: MSM的工作方式,即:一个包含触发它们的事件的转换列表:

        var S1 = new BasicTState<char>("S1");
        var S2 = new BasicTState<char>("S2");

        var transitions = new[]
        {
            new TTransition<char>{from = S1, to = S2, evt = 'b', guard = null },
            new TTransition<char>{from = S2, to = S1, evt = 'a', guard = null },
        };

        // The type parameter denotes the type of event, that is to be accepted.
        // the second parameter is the initial state!
        var sm = new TStateMachine<char>(transitions, S1);  

        sm.Event('b');

        Assert.AreEqual(sm.ActiveState, S2);

到目前为止一直很好 - 现在。鉴于这种情况,我需要几个例如字符,以匹配相同的状态转换我有点卡住。天真的方式是为每个角色定义一个状态转换来接受 - 这是乏味和错误的。我想做的是使用“Matcher”对象来完成这项工作,例如:

            var transitions = new[]
        {
            new TTransition<Matcher<char>>{from = S1, to = S2, evt = new Matcher<char>(new[] {'c', 'd'}), guard = null },
            new TTransition<Matcher<char>>{from = S2, to = S1, evt = new Matcher<char>(new[] {'e', 'f'}), guard = null },

        };

为了使其工作,我已经为匹配器定义了隐式转换,因此我仍然可以生成上例中的事件,即“sm.Event('c')”。这很好用。但是现在我的情况是我的statemachine对象必须是TStateMachine<Matcher<char>>类型,这显然很烦人,因为我现在不能使用简单的表示法,只需要匹配一个eventtype,我只能使用单一类型的匹配器,隐式类型转换将总是迫使我进入这一类型。更糟糕的是,我无法隐藏界面背后的匹配器,因为类型转换根本不会发挥作用。

我知道,我可以轻松地使用接口来解决匹配单个和多个事件类型的问题,但这会大大降低可读性(坦率地说,以这种方式解决它并不好玩)。我真正感兴趣的是,如果有任何方法可以使多态性与隐式类型转换很好地结合。我想最终会有这样的结果:

    var transitions = new[]
    {
        new TTransition<char>{from = S1, to = S2, evt = 'b', guard = null },
        new TTransition<char>{from = S2, to = S1, evt = new Matcher<char>(new[] {'e', 'f'}), guard = null },
        new TTransition<char>{from = S2, to = S1, evt = new YetAnotherMatcherTypeIDontKnowAboutYet<char>(new YadaYadaObject()), guard = null }
    };

1 个答案:

答案 0 :(得分:1)

状态本身是否需要泛型类型参数?为什么国家需要知道它是如何被触发的?

我会声明像这样的转换类型

public class Transition<TEvent>
{
    public BasicState From { get; set; }
    public BasicState To { get; set; }
    public Func<TEvent, bool> EventMatcher { get; set; }
    ...
}

和像这样的状态机

public class StateMachine<TEvent>
{
    public StateMachine(Transition<TEvent>[] transitions, BasicState initialState)
    {
        ...
    }

    ...
}

并且转换将始终声明匹配器,即使在使用单个字符的简单情况下

var transitions = new[]
{
    new Transition<char>{From = S1, To = S2, EventMatcher = c => c == 'b', Guard = null },
    new Transition<char>{From = S2, To = S1, EventMatcher = c => c == 'a', Guard = null },
};

如果您需要匹配多个字符,可以编写

new Transition<char>{From = S2, To = S1, EventMatcher = c => "ef".Contains(c), Guard = null }

注意,我已使用Lambda Expressions=>语法)。

我省略了&#34; T&#34;来自类型名称的前缀,因为这在C#中并不常见(它在Pascal中很常见)。此外,属性应该在PascalCase中,即使在C#:-)中也是如此。请参阅MSDN上的Naming Guidelines

或者,您可以声明不同类型的过渡。他们将负责匹配自己

public abstract class TransitionBase<TEvent>
    where TEvent : IEquatable<TEvent>
{
    public BasicState From { get; set; }
    public BasicState To { get; set; }
    public abstract bool DoesMatchEvent(TEvent event);
}

public class SingleEventTransition<TEvent> : TransitionBase<TEvent>
    where TEvent : IEquatable<TEvent>
{
    TEvent Event { get; set; }
    public override bool DoesMatchEvent(TEvent event)
    {
        return Event.Equals(event);
    }
}

public class MultiEventTransition<TEvent> : TransitionBase<TEvent>
    where TEvent : IEquatable<TEvent>
{
    TEvent[] Events { get; set; }
    public override bool DoesMatchEvent(TEvent event)
    {
        foreach (TEvent e in Events) {
            if (e.Equals(event)) {
                return true;
            }
        }
        return false;
    }
}

请注意,由于您事先并不知道TEvent的真实类型,因此您无法使用它,就像它是char一样;但是,您可以指定一个泛型类型约束,要求类型是等同的,即提供Equals方法。 charintstring等基本类型可以。如果您实现自己的事件类型,则可以自己实现IEquatable<T>界面。

TransitionBase<char>[] transitions = new[]
{
    new SingleEventTransition<char>{From = S1, To = S2, Event = 'b', Guard = null },
    new MultiEventTransition<char>{From = S2, To = S1, Events = new TEvent[]{ 'e', 'f'},
                                   Guard = null },
};