如何使通用类型和具体类型可互换?

时间:2010-03-10 03:15:20

标签: c# generics

我正在编写一个不可变的二叉树类,其中所有方法(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<T>而不是AbstractBinaryTree<AvlTree<T>, T>。但是我甚至无法做到这一点,因为基类不能编译。

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

3 个答案:

答案 0 :(得分:2)

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

无论如何,您可以尝试使用泛型搜索在C#中模拟自我类型的方法...

另一个指针是我前段时间看过的一篇文章。据我所知,它以与你类似的方式使用泛型,所以也许它可以给你一些提示如何解决问题。

答案 1 :(得分:2)

使用AbstractBinaryTree<TreeType, T>

    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));
}

答案 2 :(得分:2)

哇,哇,我为自己做的事情太难了,但无论如何解决方案真的非常简单:

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的实现效果非常好,因为我们在向下的路上递归插入树中,并在callstack展开时平衡树。

如果有人可以建议让我重用this而不是使用其值的副本分配新对象的方式,我想听听它,但是现在这似乎有效。