【问题标题】:Type-safe way to store two types of object in a collection在集合中存储两种类型的对象的类型安全方法
【发布时间】:2014-07-04 02:00:44
【问题描述】:

我一直在实施一种增强的 Shunting-Yard 算法来解析算术表达式。该算法的一个方面是它维护了一个Queue 和一个Stack

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

ExpressionsParenthesisOperators 没有任何共同点可以保证它们中的任何一个具有共享接口。

方法:

  • 我当前的实现包括实现INotParanthesisExpressionOperatorOperatorParanthesis 实现 INotExpression。然后我声明Queue <INotParanthesis>Stack <INotExpression>

    我不喜欢这种实现方式——这些接口看起来更像是为了更简洁的算法代码而进行的黑客攻击。我也相信接口应该描述一个对象是什么,而不是它不是什么。

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

  • 到目前为止,我唯一想到的就是实现我自己的 NonParanthesisQueueNonExpressionStack 容器。这具有对从容器中拉出的对象进行更一致的类型检查的优点 - 以及更多代码的缺点。

我的方法有什么合理的替代方案吗?

【问题讨论】:

    标签: c# collections types heterogeneous


    【解决方案1】:

    听起来你真正想要的是 sum 类型。尽管 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);
        }
    }
    

    参考资料:

    【讨论】:

    • 谢谢!这正是我想要的。
    • @Vladislav 这类似于 rationull 的答案(基本上这个想法是使用元组类型来保存异构数据)但我想你可以将两者的想法结合起来使其更短(我说这个'因为你喜欢它不那么冗长)。
    【解决方案2】:

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

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

    希望有人有更聪明的主意。

    【讨论】:

    • 谢谢 - 我没有考虑过这个问题 - 虽然有点冗长,但绝对是有道理的。
    猜你喜欢
    • 2017-11-13
    • 1970-01-01
    • 1970-01-01
    • 2012-05-24
    • 1970-01-01
    • 2021-02-18
    • 1970-01-01
    • 1970-01-01
    • 2017-03-06
    相关资源
    最近更新 更多