泛型 - 实现相互编译时类型安全的更清洁方式?

时间:2017-03-02 20:45:10

标签: c# generics type-safety

假设你有一个由“父母”和“孩子”组成的数据结构,父母和孩子之间的引用是相互的:

借用a previous post,以下代码写入保证父母与子女之间的相互参照(不包含滥用反映):

public static class Base
{
    private interface IParent
    {
        List<Child> Children { get; }
    }

    public class Parent : IParent
    {
        private readonly List<Child> _children = new List<Child>();

        public readonly ReadOnlyCollection<Child> Children = _children.AsReadOnly();

        List<Child> IParent.Children { get { return _children; } }
    }

    public class Child
    {
        private Parent _parent;

        public Parent Parent
        {
            get
            {
                return _parent;
            }
            set
            {
                if(value == _parent)
                    return;

                if(_parent != null)
                {
                    ((IParent)_parent).Children.Remove(this);
                    _parent = null;
                }

                if(value != null)
                {
                    ((IParent)value).Children.Add(this);
                    _parent = value;
                }
            }
        }
    }
}

现在假设你想要一个类似的结构,但你也想要类型安全。也就是说, TParent的实例只能引用TChild 的实例, TChild的实例只能引用TParent的实例

我提出了这个解决方案:

public static class Base<TParent, TChild>
    where TParent : Base<TParent, TChild>.Parent
    where TChild  : Base<TParent, TChild>.Child
{
    private interface IParent
    {
        List<TChild> Children { get; }
    }

    public class Parent : IParent
    {
        private readonly List<TChild> _children = new List<Child>();

        public readonly ReadOnlyCollection<TChild> Children = _children.AsReadOnly();

        List<TChild> IParent.Children { get { return _children; } }
    }

    public class Child
    {
        private TParent _parent;

        public TParent Parent
        {
            get
            {
                return _parent;
            }
            set
            {
                if(value == _parent)
                    return;

                if(_parent != null)
                {
                    // Oh no, casting!
                    ((IParent)_parent).Children.Remove((TChild)this);
                    _parent = null;
                }

                if(value != null)
                {
                    // Oh no, casting!
                    ((IParent)value).Children.Add((TChild)this);
                    _parent = value;
                }
            }
        }
    }
}

虽然这有效,但{strong> ChildTChild内投放到Child.Parent.set 的点让我有点担心。虽然我不确定是否有办法使用这个抛出InvalidCastException的类,但它可能仍然无法破解。

有更清洁的方法来实现这种效果吗?

1 个答案:

答案 0 :(得分:6)

首先,问题Generics and Parent/Child architecture的答案对于与您的父母子女关系的通用建模类似的架构有一些想法。

其次,我强烈建议不要使用这种模式。它令人困惑,可以说是滥用泛型,它实际上并不是类型安全的。见https://blogs.msdn.microsoft.com/ericlippert/2011/02/03/curiouser-and-curiouser/

第三,你的态度是编译器可能错误地认为需要进行类型转换,并且可能导致它失败,这种态度可能会导致你将来遇到麻烦。我建议相反默认情况下相信编译器是正确的,因为它说你的程序不是类型安全的,并且相信如果你认为编译器是错误的,你可能还没有想到它:

class Car : Base<Car, Wheel>.Parent { ... }
class Wheel : Base<Car, Wheel>.Child { ... }
class AnotherWheel : Base<Car, Wheel>.Child { ... }
...
new Car() blah blah blah
new Wheel() blah blah blah
the wheel is the child of the car, blah blah blah
and what happens when you make a Car the parent of an AnotherWheel?

编译器完全正确地说需要强制转换;我们无法断定转换是有效的,因为它不是。

第四,如何解决这个问题而不诉诸滥用实际上并非类型安全的泛型?

有办法做到这一点,但我不认为你会喜欢它们。例如:

  • 制作基础不可变对象。孩子不认识其父母;父母确实知道自己的孩子。
  • 在底层不可变对象之上构造一个facade对象;孩子的外观 知道其父母。
  • 当“编辑”立面时,通过构建树的新脊柱来构建新的基础不可变树。

可以使用通用接口协方差构造这样的系统,使得不需要演员表;这样做是为了锻炼。

作为一个热身练习,请考虑如何为通用不可变IStack<T>构造一个接口,这样当你将Turtle推入一堆Tigers时,它就变成了一个不可变的动物堆栈。 / p>

总的来说,我认为最好不要尝试在C#类型系统中捕获这种限制。