以类型安全的方式在集合中存储两种类型的对象

时间:2011-07-27 22:50:59

标签: c# collections types heterogeneous

我一直在实现一个增强的Shunting-Yard算法来解析算术表达式。该算法的一个方面是它保持QueueStack

在我的实施中,Queue包含ExpressionsOperatorsStack包含OperatorsParenthesis

ExpressionsParenthesisOperators没有任何共同之处,可以保证其中任何两个人拥有共享界面。

的方法:

  • 我目前的实施工作包括实施Expression的{​​{1}}和OperatorINotParanthesisOperator实施Paranthesis。然后我宣布INotExpressionQueue <INotParanthesis>

    我不喜欢这种实现 - 这些接口似乎非常适合更清晰的算法代码。我也相信接口应该描述一个对象是什么,而不是它不是什么。

  • 另一方面,我也不想使用Stack <INotExpression>的集合,因为很难确定此类代码的正确性。

  • 到目前为止,我唯一提出的是实现自己的<Object>NonParanthesisQueue容器。这样做的好处是可以对从这些容器中取出的对象进行更一致的类型检查 - 而且还有更多代码的缺点。

我的方法有没有合理的替代方案?

3 个答案:

答案 0 :(得分:4)

听起来你真正想要的是总和类型。虽然C#没有内置这些​​功能,但是你可以使用称为Church编码的函数式编程来实现这一目的。它是完全类型安全的,没有涉及强制转换,但是在C#中使用它有点奇怪,主要是由于类型推断的限制。

主要的诀窍是,我们有一个更高阶函数Map,而不是使用属性和检查来检索两个备选方案中的一个,我们有两个函数作为参数,并根据存在的替代方案调用适当的函数。以下是您将如何使用它:

var stack = new Stack<IEither<Operator, Parenthesis>>();

stack.Push(new Left<Operator, Parenthesis>(new Operator()));
stack.Push(new Right<Operator, Parenthesis>(new Parenthesis()));

while (stack.Count > 0)
{
    stack.Pop().Map(op  => Console.WriteLine("Found an operator: " + op),
                    par => Console.WriteLine("Found a parenthesis: " + par));
}

以下是IEitherLeftRight的实施。它们是完全通用的,可以在任何你想要总和类型的地方使用。

public interface IEither<TLeft, TRight>
{
    TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight);
    void Map(Action<TLeft> onLeft, Action<TRight> onRight);
}

public sealed class Left<TLeft, TRight> : IEither<TLeft, TRight>
{
    private readonly TLeft value;

    public Left(TLeft value)
    {
        this.value = value;
    }

    public TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight)
    {
        return onLeft(value);
    }

    public void Map(Action<TLeft> onLeft, Action<TRight> onRight)
    {
        onLeft(value);
    }
}

public sealed class Right<TLeft, TRight> : IEither<TLeft, TRight>
{
    private readonly TRight value;

    public Right(TRight value)
    {
        this.value = value;
    }

    public TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight)
    {
        return onRight(value);
    }

    public void Map(Action<TLeft> onLeft, Action<TRight> onRight)
    {
        onRight(value);
    }
}

参考文献:

答案 1 :(得分:1)

也许您可以为每个定义一个小持有者类型,一个具有Expression属性和一个Operator属性,另一个具有Operator属性和一个Parenthesis属性。访问者和构造函数可以断言或以其他方式确保只填充一个。队列和堆栈都包含适当的持有者类型。

有点尴尬但是类型安全且可行。

希望有人会有更聪明的想法。

答案 2 :(得分:0)

如果名字困扰你,那么IQueueable和IStackable怎么样?以下是与您类似的其他stackoverflow问题(关于标记接口)。

What is the purpose of a marker interface?

Interface without any members - bad practice?