【问题标题】:How do I make generic type and concrete type interchangeable?如何使泛型类型和具体类型可互换?
【发布时间】:2010-03-10 13:01:48
【问题描述】:

我正在编写一个不可变的二叉树类,其中所有方法(Insert、Remove、RotateLeft 等)都返回一个树的新实例,而不是就地修改它。

我将创建许多不同的树实现:Avl 树、红黑树、splay 树等。我有以下内容:

public class AbstractBinaryTree<TreeType, T>
    where TreeType : AbstractBinaryTree<TreeType, T>
    where T : IComparable<T>
{
    protected abstract TreeType CreateNode(TreeType left, T value, TreeType right);
    protected abstract T Value { get; }
    protected abstract TreeType Left { get; }
    protected abstract TreeType Right { get; }
    protected abstract bool IsNil();

    public TreeType Insert(T item)
    {
        if (this.IsNil())
        {
            return CreateNode(this, item, this);
            // ^ doesn't compile, can't convert type
            // AbstractBinaryTree<TreeType, T> to type TreeType
        }
        else
        {
            int compare = item.CompareTo(this.Value);
            if (compare < 0)
            {
                return CreateNode(this.Left.Insert(item), this.Value, this.Right);
            }
            else if (compare > 0)
            {
                return CreateNode(this.Left, this.Value, this.Right.Insert(Value));
            }
            else
            {
                return this;
                // ^ doesn't compile, can't converrt type
                // AbstractBinaryTree<TreeType, T> to type TreeType
            }
        }
    }
}

这里的想法是 AbstractBinaryTree 是一个树节点——不仅如此,它与TreeType 的类型相同。如果我能让上面的基类正常工作,那么我可以写这样的东西:

public class AvlTree<T> : AbstractBinaryTree<AvlTree<T>, T>
{
    public override AvlTree<T> Insert(T item) { return Balance(base.Insert(item)); }
}

以便我的 Insert 方法返回 AvlTree&lt;T&gt; 而不是 AbstractBinaryTree&lt;AvlTree&lt;T&gt;, T&gt;。但是我什至无法做到这一点,因为基类无法编译。

如何将 AbstractBinaryTree 的实例传递给采用 TreeType 类型的方法?

【问题讨论】:

    标签: c# generics


    【解决方案1】:

    我真的没有答案 - 只是一些可能有用的提示。我认为这适用于具有称为 self types 概念的语言(我找不到好链接的网站!)。无论如何,self 类型意味着您可以声明一个抽象基类(比如A),并且它可以有一个返回 self 类型的方法。当创建一个继承类(比如B)时,self 类型的使用将引用B(这很有趣,因为基类不知道这个类)。对于 C# 4 爱好者来说,self 类型 是协变的。

    无论如何,您可以尝试寻找一种方法来使用泛型在 C# 中模拟 self 类型...

    另一个指针指向我前段时间看过的一篇文章。据我记得,它以与您类似的方式使用泛型,因此也许它可以为您提供一些解决问题的提示。

    【讨论】:

    • +1: 非常有趣 :) 尽管 C# 不支持开箱即用,但使用抽象类很容易模拟。例如,基类A&lt;T&gt; where T : A&lt;T&gt; 可以有一个抽象方法T Self(),而派生类B : A&lt;B&gt; 可以将该方法实现为override B Self() { return this; }。实际上,我在这里 (pastebin.com/PussQDmN) 实现了这种策略,它的工作原理与宣传的完全一样。
    • +answer: 用泛型模拟自我类型是我最终确定的解决方案 :)
    【解决方案2】:

    使用AbstractBinaryTree&lt;TreeType, T&gt;

        public abstract class AbstractBinaryTree<TreeType, T>
                where TreeType : AbstractBinaryTree<TreeType, T>
                where T : IComparable<T>
            {
                protected abstract TreeType CreateNode(AbstractBinaryTree<TreeType, T> left, T value, AbstractBinaryTree<TreeType, T> right);
                protected abstract T Value { get; }
                protected abstract TreeType Left { get; }
                protected abstract TreeType Right { get; }
                protected abstract bool IsNil();
    
                public virtual AbstractBinaryTree<TreeType, T> Insert(T item)
                {
                    if (this.IsNil())
                    {
                        return CreateNode(this.Left, item, this.Right);
                        // ^ doesn't compile, can't convert type 
                        // AbstractBinaryTree<TreeType, T> to type TreeType 
                    }
                    else
                    {
                        int compare = item.CompareTo(this.Value);
                        if (compare < 0)
                        {
                            return CreateNode(this.Left.Insert(item), this.Value, this.Right);
                        }
                        else if (compare > 0)
                        {
                            return CreateNode(this.Left, this.Value, this.Right.Insert(Value));
                        }
                        else
                        {
                            return this;
                            // ^ doesn't compile, can't converrt type 
                            // AbstractBinaryTree<TreeType, T> to type TreeType 
                        }
                    } 
                }
            }
    
        public class AvlTree<T> : AbstractBinaryTree<AvlTree<T>, T>
            where T : IComparable<T>
        {
            public override AbstractBinaryTree<AvlTree<T>, T> Insert(T item)
            {
                return base.Insert(item);
            }
    }
    

    使用 Balance() 进行投射

    private AvlTree<T> Balance(AbstractBinaryTree<AvlTree<T>, T> item)
    {
        return (AvlTree<T>)item;
    }
    
    public override AbstractBinaryTree<AvlTree<T>, T> Insert(T item)
    {
        return Balance(Insert(item));
    }
    

    【讨论】:

    • AvlTree&lt;T&gt; 中的 where T : AbstractBinaryTree&lt;AvlTree&lt;T&gt;, T&gt;, IComparable&lt;T&gt; 应该只是 where T : IComparable&lt;T&gt;
    • 还有private AvlTree&lt;T&gt; Balance(AbstractBinaryTree&lt;AvlTree&lt;T&gt;, T&gt; tree);,你可以得到想要的public override AvlTree&lt;T&gt; Insert(T item)
    • 您必须将 Insert() 的返回值转换为更具体的类型。这是一个奇怪的...
    • 她想要的覆盖是return Balance(base.Insert(item));,所以她的Balance函数理论上可以作用于AbstractBinaryTree并返回一个AvlTree(因为CreateNode返回TreeType,而this也是这个中的TreeType案例)。
    • 我想我可以完成这项工作,但它似乎是一组奇怪的方法来覆盖。特别是,abstract TreeType CreateNode(AbstractBinaryTree&lt;TreeType, T&gt; left, T value, AbstractBinaryTree&lt;TreeType, T&gt; right); 方法似乎揭示了很多关于类的底层实现。
    【解决方案3】:

    哇哦,我把事情做得太难了,但无论如何解决方法真的超级简单:

    AbstractBinaryTree 已经包含 Left、Value 和 Right 属性,因此我可以使用 CreateNode(this.Left, this.Value, this.Right) 创建当前节点的副本,而不是尝试返回 this

    public abstract class AbstractBinaryTree<TreeType, T>
        where TreeType : AbstractBinaryTree<TreeType, T>
        where T : IComparable<T>
    {
        protected abstract TreeType CreateNil();
        protected abstract TreeType CreateNode(TreeType left, T value, TreeType right);
    
        protected abstract T Value { get; }
        protected abstract TreeType Left { get; }
        protected abstract TreeType Right { get; }
        protected abstract bool IsNil();
    
        public virtual TreeType Insert(T item)
        {
            if (this.IsNil())
            {
                // can't return 'this', so just creating a new nil node
                TreeType nil = CreateNil();
                return CreateNode(nil, item, nil);
            }
            else
            {
                int compare = item.CompareTo(this.Value);
                if (compare < 0)
                {
                    return CreateNode(this.Left.Insert(item), this.Value, this.Right);
                }
                else if (compare > 0)
                {
                    return CreateNode(this.Left, this.Value, this.Right.Insert(Value));
                }
                else
                {
                    // can't return 'this', so just creating a new node with a
                    // copy of the same values
                    return CreateNode(this.Left, this.Value, this.Right);
                }
            }
        }
    }
    
    public class AvlTree<T> : AbstractBinaryTree<AvlTree<T>, T>
    {
        public override AvlTree<T> Insert(T value) { return Balance(base.Insert(value)); }
    }
    

    AvlTree 的实现效果很好,因为我们在向下的过程中递归地插入到树中,并在调用堆栈展开时平衡树。

    如果有人可以建议让我重用 this 而不是分配具有其值副本的新对象的方法,我想听听,但目前这似乎可行。

    【讨论】:

    • 太棒了!是的,它似乎想要“CreateNode”或“this”,这让它变得很困难。
    • 这很漂亮。 :) 此外,如果您想摆脱树副本,if (this.IsNil()) { return CreateNode(this.Left, item, this.Right); } 应该没问题(假设 Nil.Left 为 Nil),这也将消除对 CreateNil() 的需要,您可能也可以像演员一样this 即使您对演员阵容的类型安全性感到非常偏执,也不需要(通常)插入时的副本等于... return (this as TreeType) ?? CreateNode(this.Left, this.Value, this.Right)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多